a3cee65ed4
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@41077 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
350 lines
10 KiB
Python
350 lines
10 KiB
Python
#----------------------------------------------------------------------
|
|
# Name: wxPython.lib.pyshell
|
|
# Purpose: A Python Interactive Interpreter running in a wxStyledTextCtrl
|
|
# window.
|
|
#
|
|
# Author: Robin Dunn
|
|
#
|
|
# Created: 7-July-2000
|
|
# RCS-ID: $Id$
|
|
# Copyright: (c) 2000 by Total Control Software
|
|
# Licence: wxWindows license
|
|
#----------------------------------------------------------------------
|
|
# 12/10/2003 - Jeff Grimmett (grimmtooth@softhome.net)
|
|
#
|
|
# o 2.5 compatability update.
|
|
# o Added deprecation warning.
|
|
#
|
|
|
|
"""
|
|
PyShellWindow is a class that provides an Interactive Interpreter running
|
|
inside a wxStyledTextCtrl, similar to the Python shell windows found in
|
|
IDLE and PythonWin.
|
|
|
|
There is still much to be done to improve this class, such as line
|
|
buffering/recall, autoindent, calltips, autocomplete, fixing the colourizer,
|
|
etc... But it's a good start.
|
|
|
|
|
|
8-10-2001 THIS MODULE IS NOW DEPRECATED. Please see the most excellent
|
|
PyCrust package instead.
|
|
|
|
"""
|
|
|
|
import keyword
|
|
import sys
|
|
import warnings
|
|
|
|
from code import InteractiveInterpreter
|
|
|
|
import wx
|
|
import wx.stc as stc
|
|
|
|
warningmsg = r"""\
|
|
|
|
########################################\
|
|
# THIS MODULE IS NOW DEPRECATED |
|
|
# |
|
|
# Please see the most excellent PyCrust |
|
|
# package instead. |
|
|
########################################/
|
|
|
|
"""
|
|
|
|
warnings.warn(warningmsg, DeprecationWarning, stacklevel=2)
|
|
|
|
#----------------------------------------------------------------------
|
|
# default styles, etc. to use for the STC
|
|
|
|
if wx.Platform == '__WXMSW__':
|
|
_defaultSize = 8
|
|
else:
|
|
_defaultSize = 10
|
|
|
|
|
|
_default_properties = {
|
|
'selMargin' : 0,
|
|
'marginWidth' : 1,
|
|
'ps1' : '>>> ',
|
|
'stdout' : 'fore:#0000FF',
|
|
'stderr' : 'fore:#007f00',
|
|
'trace' : 'fore:#FF0000',
|
|
|
|
'default' : 'size:%d' % _defaultSize,
|
|
'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
|
|
'bracebad' : 'fore:#000000,back:#FF0000,bold',
|
|
|
|
# properties for the various Python lexer styles
|
|
'comment' : 'fore:#007F00',
|
|
'number' : 'fore:#007F7F',
|
|
'string' : 'fore:#7F007F,italic',
|
|
'char' : 'fore:#7F007F,italic',
|
|
'keyword' : 'fore:#00007F,bold',
|
|
'triple' : 'fore:#7F0000',
|
|
'tripledouble': 'fore:#7F0000',
|
|
'class' : 'fore:#0000FF,bold,underline',
|
|
'def' : 'fore:#007F7F,bold',
|
|
'operator' : 'bold',
|
|
|
|
}
|
|
|
|
|
|
# new style numbers
|
|
_stdout_style = 15
|
|
_stderr_style = 16
|
|
_trace_style = 17
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
class PyShellWindow(stc.StyledTextCtrl, InteractiveInterpreter):
|
|
def __init__(self, parent, ID, pos=wx.DefaultPosition,
|
|
size=wx.DefaultSize, style=0,
|
|
locals=None, properties=None, banner=None):
|
|
stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
|
|
InteractiveInterpreter.__init__(self, locals)
|
|
|
|
self.lastPromptPos = 0
|
|
|
|
# the line cache is used to cycle through previous commands
|
|
self.lines = []
|
|
self.lastUsedLine = self.curLine = 0
|
|
|
|
# set defaults and then deal with any user defined properties
|
|
self.props = {}
|
|
self.props.update(_default_properties)
|
|
if properties:
|
|
self.props.update(properties)
|
|
self.UpdateProperties()
|
|
|
|
# copyright/banner message
|
|
if banner is None:
|
|
self.write("Python %s on %s\n" % #%s\n(%s)\n" %
|
|
(sys.version, sys.platform,
|
|
#sys.copyright, self.__class__.__name__
|
|
))
|
|
else:
|
|
self.write("%s\n" % banner)
|
|
|
|
# write the initial prompt
|
|
self.Prompt()
|
|
|
|
# Event handlers
|
|
self.Bind(wx.EVT_KEY_DOWN, self.OnKey)
|
|
self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI, id=ID)
|
|
#self.Bind(stc.EVT_STC_STYLENEEDED, self.OnStyle, id=ID)
|
|
|
|
|
|
def GetLocals(self): return self.locals
|
|
def SetLocals(self, locals): self.locals = locals
|
|
|
|
def GetProperties(self): return self.props
|
|
def SetProperties(self, properties):
|
|
self.props.update(properties)
|
|
self.UpdateProperties()
|
|
|
|
|
|
def UpdateProperties(self):
|
|
"""
|
|
Reset the editor and other settings based on the contents of the
|
|
current properties dictionary.
|
|
"""
|
|
p = self.props
|
|
|
|
#self.SetEdgeMode(stc.STC_EDGE_LINE)
|
|
#self.SetEdgeColumn(80)
|
|
|
|
|
|
# set the selection margin and window margin
|
|
self.SetMarginWidth(1, p['selMargin'])
|
|
self.SetMargins(p['marginWidth'], p['marginWidth'])
|
|
|
|
# styles
|
|
self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
|
|
self.StyleClearAll()
|
|
self.StyleSetSpec(_stdout_style, p['stdout'])
|
|
self.StyleSetSpec(_stderr_style, p['stderr'])
|
|
self.StyleSetSpec(_trace_style, p['trace'])
|
|
|
|
self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
|
|
self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
|
|
self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
|
|
self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
|
|
self.StyleSetSpec(stc.STC_P_STRING, p['string'])
|
|
self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
|
|
self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
|
|
self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
|
|
self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
|
|
self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
|
|
self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
|
|
self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
|
|
self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
|
|
|
|
|
|
# used for writing to stdout, etc.
|
|
def _write(self, text, style=_stdout_style):
|
|
self.lastPromptPos = 0
|
|
pos = self.GetCurrentPos()
|
|
self.AddText(text)
|
|
self.StartStyling(pos, 0xFF)
|
|
self.SetStyling(len(text), style)
|
|
self.EnsureCaretVisible()
|
|
wx.Yield()
|
|
|
|
write = _write
|
|
|
|
def writeTrace(self, text):
|
|
self._write(text, _trace_style)
|
|
|
|
|
|
def Prompt(self):
|
|
# is the current line non-empty?
|
|
text, pos = self.GetCurLine()
|
|
if pos != 0:
|
|
self.AddText('\n')
|
|
self.AddText(self.props['ps1'])
|
|
self.lastPromptPos = self.GetCurrentPos()
|
|
self.EnsureCaretVisible()
|
|
self.ScrollToColumn(0)
|
|
|
|
|
|
def PushLine(self, text):
|
|
# TODO: Add the text to the line cache, manage the cache so
|
|
# it doesn't get too big.
|
|
pass
|
|
|
|
|
|
|
|
def OnKey(self, evt):
|
|
key = evt.GetKeyCode()
|
|
if key == wx.WXK_RETURN:
|
|
pos = self.GetCurrentPos()
|
|
lastPos = self.GetTextLength()
|
|
|
|
# if not on the last line, duplicate the current line
|
|
if self.GetLineCount()-1 != self.GetCurrentLine():
|
|
text, col = self.GetCurLine()
|
|
prompt = self.props['ps1']
|
|
lp = len(prompt)
|
|
if text[:lp] == prompt:
|
|
text = text[lp:]
|
|
|
|
self.SetSelection(self.lastPromptPos, lastPos)
|
|
self.ReplaceSelection(text[:-1])
|
|
|
|
else: # try to execute the text from the prompt to the end
|
|
if lastPos == self.lastPromptPos:
|
|
self.AddText('\n')
|
|
self.Prompt()
|
|
return
|
|
|
|
text = self.GetTextRange(self.lastPromptPos, lastPos)
|
|
self.AddText('\n')
|
|
|
|
more = self.runsource(text)
|
|
if not more:
|
|
self.PushLine(text)
|
|
self.Prompt()
|
|
|
|
# TODO: Add handlers for Alt-P and Alt-N to cycle through entries
|
|
# in the line cache
|
|
|
|
else:
|
|
evt.Skip()
|
|
|
|
|
|
def OnStyle(self, evt):
|
|
# Only style from the prompt pos to the end
|
|
lastPos = self.GetTextLength()
|
|
if self.lastPromptPos and self.lastPromptPos != lastPos:
|
|
self.SetLexer(stc.STC_LEX_PYTHON)
|
|
self.SetKeywords(0, ' '.join(keyword.kwlist))
|
|
|
|
self.Colourise(self.lastPromptPos, lastPos)
|
|
|
|
self.SetLexer(0)
|
|
|
|
|
|
def OnUpdateUI(self, evt):
|
|
# check for matching braces
|
|
braceAtCaret = -1
|
|
braceOpposite = -1
|
|
charBefore = None
|
|
caretPos = self.GetCurrentPos()
|
|
if caretPos > 0:
|
|
charBefore = self.GetCharAt(caretPos - 1)
|
|
styleBefore = self.GetStyleAt(caretPos - 1)
|
|
|
|
# check before
|
|
if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
|
|
braceAtCaret = caretPos - 1
|
|
|
|
# check after
|
|
if braceAtCaret < 0:
|
|
charAfter = self.GetCharAt(caretPos)
|
|
styleAfter = self.GetStyleAt(caretPos)
|
|
if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
|
|
braceAtCaret = caretPos
|
|
|
|
if braceAtCaret >= 0:
|
|
braceOpposite = self.BraceMatch(braceAtCaret)
|
|
|
|
if braceAtCaret != -1 and braceOpposite == -1:
|
|
self.BraceBadlight(braceAtCaret)
|
|
else:
|
|
self.BraceHighlight(braceAtCaret, braceOpposite)
|
|
|
|
|
|
|
|
#----------------------------------------------
|
|
# overloaded methods from InteractiveInterpreter
|
|
def runsource(self, source):
|
|
stdout, stderr = sys.stdout, sys.stderr
|
|
sys.stdout = FauxFile(self, _stdout_style)
|
|
sys.stderr = FauxFile(self, _stderr_style)
|
|
|
|
more = InteractiveInterpreter.runsource(self, source)
|
|
|
|
sys.stdout, sys.stderr = stdout, stderr
|
|
return more
|
|
|
|
def showsyntaxerror(self, filename=None):
|
|
self.write = self.writeTrace
|
|
InteractiveInterpreter.showsyntaxerror(self, filename)
|
|
self.write = self._write
|
|
|
|
def showtraceback(self):
|
|
self.write = self.writeTrace
|
|
InteractiveInterpreter.showtraceback(self)
|
|
self.write = self._write
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
class FauxFile:
|
|
def __init__(self, psw, style):
|
|
self.psw = psw
|
|
self.style = style
|
|
|
|
def write(self, text):
|
|
self.psw.write(text, self.style)
|
|
|
|
def writelines(self, lst):
|
|
map(self.write, lst)
|
|
|
|
def flush(self):
|
|
pass
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
# test code
|
|
|
|
if __name__ == '__main__':
|
|
app = wx.PyWidgetTester(size = (640, 480))
|
|
app.SetWidget(PyShellWindow, -1)
|
|
app.MainLoop()
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
|