#----------------------------------------------------------------------
# Name:        wxPython.lib.pyshell
# Purpose:     A Python Interactive Interpreter running in a wxStyledTextCtrl
#              window.
#
# Author:      Robin Dunn
#
# Created:     7-July-2000
# RCS-ID:      $Id: pyshell.py,v 1.6.2.2 2003/01/20 23:41:15 RD Exp $
# Copyright:   (c) 2000 by Total Control Software
# Licence:     wxWindows license
#----------------------------------------------------------------------

"""
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.

"""


from wxPython.wx  import *
from wxPython.stc import *

import sys, keyword
from code import InteractiveInterpreter

#----------------------------------------------------------------------
# default styles, etc. to use for the STC

if wxPlatform == '__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(wxStyledTextCtrl, InteractiveInterpreter):
    def __init__(self, parent, ID, pos=wxDefaultPosition,
                 size=wxDefaultSize, style=0,
                 locals=None, properties=None, banner=None):
        wxStyledTextCtrl.__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
        EVT_KEY_DOWN(self, self.OnKey)
        EVT_STC_UPDATEUI(self, ID, self.OnUpdateUI)
        #EVT_STC_STYLENEEDED(self, ID, self.OnStyle)


    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(wxSTC_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(wxSTC_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(wxSTC_STYLE_BRACELIGHT, p['bracegood'])
        self.StyleSetSpec(wxSTC_STYLE_BRACEBAD, p['bracebad'])
        self.StyleSetSpec(wxSTC_P_COMMENTLINE, p['comment'])
        self.StyleSetSpec(wxSTC_P_NUMBER, p['number'])
        self.StyleSetSpec(wxSTC_P_STRING, p['string'])
        self.StyleSetSpec(wxSTC_P_CHARACTER, p['char'])
        self.StyleSetSpec(wxSTC_P_WORD, p['keyword'])
        self.StyleSetSpec(wxSTC_P_TRIPLE, p['triple'])
        self.StyleSetSpec(wxSTC_P_TRIPLEDOUBLE, p['tripledouble'])
        self.StyleSetSpec(wxSTC_P_CLASSNAME, p['class'])
        self.StyleSetSpec(wxSTC_P_DEFNAME, p['def'])
        self.StyleSetSpec(wxSTC_P_OPERATOR, p['operator'])
        self.StyleSetSpec(wxSTC_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()
        wxYield()

    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.KeyCode()
        if key == 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(wxSTC_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 == wxSTC_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 == wxSTC_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 = wxPyWidgetTester(size = (640, 480))
    app.SetWidget(PyShellWindow, -1)
    app.MainLoop()


#----------------------------------------------------------------------


