#-----------------------------------------------------------------------------
# Name:        StyledTextCtrls.py
# Purpose:     Mixin classes to extend wx.stc.StyledTextCtrl
#
# Author:      Riaan Booysen
#
# Created:     2000/04/26
# RCS-ID:      $Id: StyledTextCtrls.py,v 1.39 2007/07/02 15:01:16 riaan Exp $
# Copyright:   (c) 1999 - 2007 Riaan Booysen
# Licence:     GPL
#-----------------------------------------------------------------------------

import os, re, keyword, string

import wx
import wx.stc

eols = {  wx.stc.STC_EOL_CRLF : '\r\n',
          wx.stc.STC_EOL_CR   : '\r',
          wx.stc.STC_EOL_LF   : '\n'}

import Preferences
from Utils import _

import methodparse
import STCStyleEditor

# from PythonWin from IDLE :)
_is_block_opener = re.compile(r':\s*(#.*)?$').search
_is_block_closer = re.compile(r'''
    \s*
    ( return
    | break
    | continue
    | raise
    | pass
    )
    \b
''', re.VERBOSE).match


def ver_tot(ma, mi, re):
    return ma*10000+mi*100+re

word_delim  = string.letters + string.digits + '_'
object_delim = word_delim + '.'

#---Utility mixins--------------------------------------------------------------

class FoldingStyledTextCtrlMix:
    def __init__(self, wId, margin):
        self.__fold_margin = margin
        if Preferences.edSTCFolding:
            self.SetProperty('fold', '1')
        self.SetMarginType(margin, wx.stc.STC_MARGIN_SYMBOL)
        self.SetMarginMask(margin, wx.stc.STC_MASK_FOLDERS)
        self.SetMarginSensitive(margin, True)
        self.SetMarginWidth(margin, Preferences.STCFoldingMarginWidth)

        markIdnt, markBorder, markCenter = Preferences.STCFoldingClose
        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDER, 
              markIdnt, markBorder, markCenter)
        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEREND, 
              wx.stc.STC_MARK_EMPTY, markBorder, markCenter)

        markIdnt, markBorder, markCenter = Preferences.STCFoldingOpen
        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPEN, 
              markIdnt, markBorder, markCenter)
        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPENMID, 
              wx.stc.STC_MARK_EMPTY, markBorder, markCenter)

        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERMIDTAIL, 
              wx.stc.STC_MARK_BACKGROUND, "white", "black")
        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERSUB, 
              wx.stc.STC_MARK_BACKGROUND, "white", "black")
        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERTAIL, 
              wx.stc.STC_MARK_BACKGROUND, "white", "black")

    def OnMarginClick(self, evt):
        # fold and unfold as needed
        if evt.GetMargin() == self.__fold_margin:
            if evt.GetShift() and evt.GetControl():
                self.FoldAll()
            else:
                lineClicked = self.LineFromPosition(evt.GetPosition())
                if self.GetFoldLevel(lineClicked) & wx.stc.STC_FOLDLEVELHEADERFLAG:
                    if evt.GetShift():
                        self.SetFoldExpanded(lineClicked, True)
                        self.Expand(lineClicked, True, True, 1)
                    elif evt.GetControl():
                        if self.GetFoldExpanded(lineClicked):
                            self.SetFoldExpanded(lineClicked, False)
                            self.Expand(lineClicked, False, True, 0)
                        else:
                            self.SetFoldExpanded(lineClicked, True)
                            self.Expand(lineClicked, True, True, 100)
                    else:
                        self.ToggleFold(lineClicked)


    def FoldAll(self, expanding=None):
        lineCount = self.GetLineCount()

        if expanding is None:
            expanding = True
    
            # find out if we are folding or unfolding
            for lineNum in range(lineCount):
                if self.GetFoldLevel(lineNum) & wx.stc.STC_FOLDLEVELHEADERFLAG:
                    expanding = not self.GetFoldExpanded(lineNum)
                    break

        lineNum = 0
        while lineNum < lineCount:
            level = self.GetFoldLevel(lineNum)
            if level & wx.stc.STC_FOLDLEVELHEADERFLAG and \
               (level & wx.stc.STC_FOLDLEVELNUMBERMASK) == wx.stc.STC_FOLDLEVELBASE:

                if expanding:
                    self.SetFoldExpanded(lineNum, True)
                    lineNum = self.Expand(lineNum, True)
                    lineNum = lineNum - 1
                else:
                    lastChild = self.GetLastChild(lineNum, -1)
                    self.SetFoldExpanded(lineNum, False)
                    if lastChild > lineNum:
                        self.HideLines(lineNum+1, lastChild)

            lineNum = lineNum + 1

    def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
        lastChild = self.GetLastChild(line, level)
        line = line + 1
        while line <= lastChild:
            if force:
                if visLevels > 0:
                    self.ShowLines(line, line)
                else:
                    self.HideLines(line, line)
            else:
                if doExpand:
                    self.ShowLines(line, line)

            if level == -1:
                level = self.GetFoldLevel(line)

            if level & wx.stc.STC_FOLDLEVELHEADERFLAG:
                if force:
                    if visLevels > 1:
                        self.SetFoldExpanded(line, True)
                    else:
                        self.SetFoldExpanded(line, False)
                    line = self.Expand(line, doExpand, force, visLevels-1)

                else:
                    if doExpand and self.GetFoldExpanded(line):
                        line = self.Expand(line, True, force, visLevels-1)
                    else:
                        line = self.Expand(line, False, force, visLevels-1)
            else:
                line = line + 1;

        return line

def idWord(line, piv, lineStart, leftDelim=word_delim, rightDelim=word_delim):
    if piv >= len(line):
        return 0, 0
    pivL = pivR = piv
    # Look left
    for pivL in range(piv, -1, -1):
        if not line[pivL] in leftDelim:
            pivL = pivL + 1
            break
    # Look right
    for pivR in range(piv + 1, len(line)):
        if not line[pivR] in rightDelim:
            break
    else:
        pivR = pivR+1

    return pivL + lineStart, pivR - pivL

class BrowseStyledTextCtrlMix:
    """ This class is to be mix-in with a wxStyledTextCtrl to add
        functionality for browsing the code.
    """
    def __init__(self, indicator=0):
        self.handCrs = 1
        self.stndCrs = 0

        self.IndicatorSetStyle(indicator, wx.stc.STC_INDIC_PLAIN)
        self.IndicatorSetForeground(indicator, wx.BLUE)
        self._indicator = indicator
        self.styleStart = 0
        self.styleLength = 0
        self.ctrlDown = False
        self.Bind(wx.EVT_MOTION, self.OnBrowseMotion)
        self.Bind(wx.EVT_LEFT_DOWN, self.OnBrowseClick)
        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)

    def doClearBrwsLn(self):
        self.styleStart, self.styleLength = \
            self.clearUnderline(self.styleStart, self.styleLength)

    def BrowseClick(self, word, line, lineNo, start, style):
        """Called when a link is clicked.
           Override to use, return True if click is swallowed """
        return False

    def StyleVeto(self, style):
        return False

    # XXX Setting the cursor irrevocably changes the cursor for the whole STC
    def underlineWord(self, start, length):
        #self.SetCursor(self.handCrs)
        self.SetLexer(wx.stc.STC_LEX_NULL)

        self.StartStyling(start, wx.stc.STC_INDICS_MASK)
        self.SetStyling(length, wx.stc.STC_INDIC0_MASK)
        return start, length

    def clearUnderline(self, start, length):
        #self.SetCursor(self.stndCrs)

        self.StartStyling(start, wx.stc.STC_INDICS_MASK)
        self.SetStyling(length, 0)
        self.SetLexer(wx.stc.STC_LEX_PYTHON)
        self.Refresh(False)
        return 0, 0

    def getBrowsableText(self, line, piv, lnStPs):
        return idWord(line, piv, lnStPs)

    def OnBrowseMotion(self, event):
        event.Skip()
        #check if words should be underlined
        if event.ControlDown():
            mp = event.GetPosition()
            pos = self.PositionFromPoint(wx.Point(mp.x, mp.y))

            stl = self.GetStyleAt(pos) & 31

            if self.StyleVeto(stl):
                if self.styleLength > 0:
                    self.styleStart, self.styleLength = \
                      self.clearUnderline(self.styleStart, self.styleLength)
                return

            lnNo = self.LineFromPosition(pos)
            lnStPs = self.PositionFromLine(lnNo)
            line = self.GetLine(lnNo)
            piv = pos - lnStPs
            start, length = self.getBrowsableText(line, piv, lnStPs)
            #mark new
            if length > 0 and self.styleStart != start:
                if self.styleLength > 0:
                    self.clearUnderline(self.styleStart, self.styleLength)
                self.styleStart,self.styleLength = \
                  self.underlineWord(start, length)
            #keep current
            elif self.styleStart == start: pass
            #delete previous
            elif self.styleLength > 0:
                self.styleStart, self.styleLength = \
                  self.clearUnderline(self.styleStart, self.styleLength)

        #clear any underlined words
        elif self.styleLength > 0:
            self.styleStart, self.styleLength = \
              self.clearUnderline(self.styleStart, self.styleLength)

    def getStyledWordElems(self, styleStart, styleLength):
        if styleLength > 0:
            lnNo = self.LineFromPosition(styleStart)
            lnStPs = self.PositionFromLine(lnNo)
            line = self.GetLine(lnNo)
            start = styleStart - lnStPs
            word = line[start:start+styleLength]
            return word, line, lnNo, start
        else:
            return '', 0, 0, 0

    def OnBrowseClick(self, event):
        word, line, lnNo, start = self.getStyledWordElems(self.styleStart, self.styleLength)
        if word:
            style = self.GetStyleAt(self.styleStart) & 31
            if self.BrowseClick(word, line, lnNo, start, style):
                return
        event.Skip()

    def OnKeyDown(self, event):
        if event.ControlDown(): self.ctrlDown = True
        event.Skip()

    def OnKeyUp(self, event):
        if self.ctrlDown and (not event.ControlDown()):
            self.ctrlDown = False
            if self.styleLength > 0:
                self.styleStart, self.styleLength = \
                  self.clearUnderline(self.styleStart, self.styleLength)
        event.Skip()

class CodeHelpStyledTextCtrlMix:
    def getCurrLineInfo(self):
        pos = self.GetCurrentPos()
        lnNo = self.GetCurrentLine()
        lnStPs = self.PositionFromLine(lnNo)
        return (pos, lnNo, lnStPs,
                self.GetCurLine()[0], pos - lnStPs - 1)

    def getFirstContinousBlock(self, docs):
        docs = docs.strip()
        res = []
        for line in docs.split('\n'):
            if line.strip():
                res.append(line)
            else:
                break
        return '\n'.join(res)


class AutoCompleteCodeHelpSTCMix(CodeHelpStyledTextCtrlMix):
    """ Mixin that assists with code completion

        Users should implement:
        def getCodeCompOptions(self, word, rootWord, matchWord, lnNo):
            return ['list', 'of', 'options']
    """

    def __init__(self):
        self.AutoCompSetIgnoreCase(True)
        self.AutoCompSetCancelAtStart(False)

    def codeCompCheck(self):
        pos, lnNo, lnStPs, line, piv = self.getCurrLineInfo()

        start, length = idWord(line, piv, lnStPs, object_delim, object_delim)
        startLine = start-lnStPs
        word = line[startLine:startLine+length]
        pivword = piv - startLine

        dot = word.rfind('.', 0, pivword+1)
        matchWord = word
        if dot != -1:
            rdot = word.find('.', pivword)
            if rdot != -1:
                matchWord = word[dot+1:rdot]
            else:
                matchWord = word[dot+1:]

            offset = pivword - dot
            rootWord = word[:dot]
        else:
            offset = pivword + 1
            rootWord = ''

        if not matchWord:
            offset = 0

        names = self.getCodeCompOptions(word, rootWord, matchWord, lnNo)

        # remove duplicates and sort
        unqNms = {}
        for name in names: unqNms[name] = None
        names = unqNms.keys()

        sortnames = [(name.upper(), name) for name in names]
        sortnames.sort()
        names = [n[1] for n in sortnames]

        # move _* names to the end of the list
        cnt = 0
        maxmoves = len(names)
        while cnt < maxmoves:
            if names[0] and names[0][0] == '_':
                names.append(names[0])
                del names[0]
                cnt = cnt + 1
            else:
                break

        if names:
            self.AutoCompShow(offset, ' '.join(names))
        #self.AutoCompSelect(matchWord)

class CallTipCodeHelpSTCMix(CodeHelpStyledTextCtrlMix):
    """ Mixin that assists with code completion

        Users should implement:
            def getTipValue(word, lnNo):
                return 'Tip'
    """
    def __init__(self):
        self.lastCallTip = ''
        self.lastTipHilite = (0, 0)

        self.CallTipSetBackground(Preferences.STCCallTipBackColour)

    def getTipValue(self, word, lnNo):
        return ''

    def callTipCheck(self):
        pos, lnNo, lnStPs, line, piv = self.getCurrLineInfo()

        bracket = methodparse.matchbracket(line[:piv+1], '(')
        if bracket == -1 and self.CallTipActive():
            self.CallTipCancel()
            return

        cursBrktOffset = piv - bracket

        start, length = idWord(line, bracket-1, lnStPs, object_delim, object_delim)
        startLine = start-lnStPs
        word = line[startLine:startLine+length]
        if word:
            tip = self.getTipValue(word, lnNo)
            if tip:
                # Minus offset of 1st bracket in the tip
                tipBrkt = tip.find('(')
                if tipBrkt != -1:
                    pos = pos - tipBrkt - 1
                else:
                    tipBrkt = 0

                # get the current parameter from source
                paramNo = len(methodparse.safesplitfields(\
                      line[bracket+1:piv+1]+'X', ','))
                if paramNo:
                    paramNo = paramNo - 1

                # get hilight & corresponding parameter from tip
                tipBrktEnd = tip.rfind(')')
                tip_param_str = tip[tipBrkt+1:tipBrktEnd]
                tip_params = methodparse.safesplitfields(\
                    tip_param_str, ',', ('(', '{'), (')', '}') )
                try:
                    hiliteStart = tipBrkt+1 + tip_param_str.find(tip_params[paramNo])
                except IndexError:
                    hilite = (0, 0)
                else:
                    hilite = (hiliteStart,
                              hiliteStart+len(tip_params[paramNo]))

                # don't update if active and unchanged
                if self.CallTipActive() and tip == self.lastCallTip and \
                      hilite == self.lastTipHilite:
                    return

                # close if active and changed
                if self.CallTipActive() and (tip != self.lastCallTip or \
                      hilite != self.lastTipHilite):
                    self.CallTipCancel()

                self.CallTipShow(pos - cursBrktOffset, tip)

                self.CallTipSetHighlight(hilite[0], hilite[1])
                self.lastCallTip = tip
                self.lastTipHilite = hilite

class DebuggingViewSTCMix:
    def __init__(self, debugMarkers):

        (self.brkPtMrk, self.tmpBrkPtMrk, self.disabledBrkPtMrk,
         self.stepPosMrk) = debugMarkers

        # XXX properly allocate the marker
        self.stepPosBackMrk = self.stepPosMrk + 1

        # Initialize breakpoints from file and running debugger
        # XXX Remote breakpoints should be stored in a local pickle
        filename = self.model.filename

        from Debugger.Breakpoint import bplist
        self.breaks = bplist.getFileBreakpoints(filename)
        self.tryLoadBreakpoints()

    def setupDebuggingMargin(self, symbolMrg):
        self.SetMarginType(symbolMrg, wx.stc.STC_MARGIN_SYMBOL)
        self.SetMarginWidth(symbolMrg, Preferences.STCSymbolMarginWidth)
        self.SetMarginSensitive(symbolMrg, True)

        markIdnt, markBorder, markCenter = Preferences.STCBreakpointMarker
        self.MarkerDefine(self.brkPtMrk, markIdnt, markBorder, markCenter)

        markIdnt, markBorder, markCenter = Preferences.STCLinePointer
        self.MarkerDefine(self.stepPosMrk, markIdnt, markBorder, markCenter)

        markIdnt, markBorder, markCenter = Preferences.STCTmpBreakpointMarker
        self.MarkerDefine(self.tmpBrkPtMrk, markIdnt, markBorder, markCenter)

        markIdnt, markBorder, markCenter = Preferences.STCDisabledBreakpointMarker
        self.MarkerDefine(self.disabledBrkPtMrk, markIdnt, markBorder, markCenter)

        try:
            wx.stc.STC_MARK_BACKGROUND
        except:
            self.MarkerDefine(self.stepPosBackMrk, wx.stc.STC_MARK_EMPTY,
                              wx.Colour(255, 255, 255), wx.Colour(128, 128, 255))
        else:
            self.MarkerDefine(self.stepPosBackMrk, wx.stc.STC_MARK_BACKGROUND,
                              wx.Colour(255, 255, 255), wx.Colour(220, 220, 255))


    def setInitialBreakpoints(self):
        # Adds markers where the breakpoints are located.
        for mrk in (self.tmpBrkPtMrk, self.disabledBrkPtMrk, self.brkPtMrk):
            self.MarkerDeleteAll(mrk)
        for brk in self.breaks.listBreakpoints():
            self.setBreakMarker(brk)

    def setBreakMarker(self, brk):
        if brk['temporary']: mrk = self.tmpBrkPtMrk
        elif not brk['enabled']: mrk = self.disabledBrkPtMrk
        else: mrk = self.brkPtMrk
        lineno = brk['lineno'] - 1
        currMrk = self.MarkerGet(lineno) & (1 << mrk)
        if currMrk:
            self.MarkerDelete(lineno, mrk)
        self.MarkerAdd(lineno, mrk)

    def deleteBreakMarkers(self, lineNo):
        self.MarkerDelete(lineNo - 1, self.brkPtMrk)
        self.MarkerDelete(lineNo - 1, self.tmpBrkPtMrk)
        self.MarkerDelete(lineNo - 1, self.disabledBrkPtMrk)

    def deleteBreakPoint(self, lineNo):
        self.breaks.deleteBreakpoints(lineNo)
        debugger = self.model.editor.debugger
        if debugger:
            # Try to apply to the running debugger.
            debugger.deleteBreakpoints(self.model.filename, lineNo)
        self.deleteBreakMarkers(lineNo)

    def addBreakPoint(self, lineNo, temp=0, notify_debugger=1):
        filename = self.model.filename

        self.breaks.addBreakpoint(lineNo, temp)
        if notify_debugger:
            debugger = self.model.editor.debugger
            if debugger:
                # Try to apply to the running debugger.
                debugger.setBreakpoint(filename, lineNo, temp)
        if temp: mrk = self.tmpBrkPtMrk
        else: mrk = self.brkPtMrk
        self.MarkerAdd(lineNo - 1, mrk)

    def moveBreakpoint(self, bpt, delta):
        # remove
        index = (bpt.file, bpt.line)

        if index not in bpt.bplist.keys():
            return

        bpt.bplist[index].remove(bpt)
        if not bpt.bplist[index]:
            del bpt.bplist[index]

        del self.breaks[bpt.line]

        bpt.line = bpt.line + delta

        # re-add
        index = (bpt.file, bpt.line)
        if bpt.bplist.has_key(index):
            bpt.bplist[index].append(bpt)
        else:
            bpt.bplist[index] = [bpt]

        self.breaks[bpt.line] = bpt

    def tryLoadBreakpoints(self):
        import pickle
        fn = self.getBreakpointFilename()

        if fn:
            rval = self.breaks.loadBreakpoints(fn)
            if rval:
                self.setInitialBreakpoints()
        else:
            rval = None
        return rval

    def saveBreakpoints(self):
        # XXX This is not yet called automatically on saving a module,
        # should it be ?
        fn = self.getBreakpointFilename()
        if fn:
            self.breaks.saveBreakpoints(fn)

    def clearStepPos(self, lineNo):
        if lineNo < 0:
            lineNo = 0
        self.MarkerDelete(lineNo, self.stepPosMrk)
        self.MarkerDelete(lineNo, self.stepPosBackMrk)

    def setStepPos(self, lineNo):
        if lineNo < 0:
            lineNo = 0
        self.MarkerDeleteAll(self.stepPosMrk)
        self.MarkerAdd(lineNo, self.stepPosMrk)
        self.MarkerDeleteAll(self.stepPosBackMrk)
        self.MarkerAdd(lineNo, self.stepPosBackMrk)

        if self.breaks.hasBreakpoint(lineNo + 1):
            # Be sure all the breakpoints for this file are displayed.
            self.setInitialBreakpoints()
        self.MarkerDelete(lineNo, self.tmpBrkPtMrk)

    def getBreakpointFilename(self):
        try:
            return os.path.splitext(self.model.assertLocalFile())[0]+'.brk'
        except AssertionError:
            return ''

    def adjustBreakpoints(self, linesAdded, modType, evtPos):
        line = self.LineFromPosition(evtPos)#event.GetPosition())

        endline = self.GetLineCount()
        if self.breaks.hasBreakpoint(
              min(line, endline), max(line, endline)):

            changed = self.breaks.adjustBreakpoints(line, linesAdded)
            debugger = self.model.editor.debugger
            if debugger:
                # XXX also check that module has been imported
                # XXX this should apply; with or without breakpoint
                if debugger.running and \
                      not modType & wx.stc.STC_PERFORMED_UNDO:
                    wx.LogWarning(_('Adding or removing lines from the '
                                    'debugger will cause the source and the '
                                    'debugger to be out of sync.'
                                    '\nPlease undo this action.'))

                debugger.adjustBreakpoints(self.model.filename, line,
                      linesAdded)
            else:
                # bdb must still be updated
                import bdb

                bpList = bdb.Breakpoint.bplist
                filename = self.model.filename #canonic form, same as url form I think
                setBreaks = []

                # store reference and remove from (fn, ln) refed dict.
                for bpFile, bpLine in bpList.keys():
                    if bpFile == filename and bpLine > line:
                        setBreaks.append(bpList[bpFile, bpLine])
                        del bpList[bpFile, bpLine]

                # put old break at new place and renumber
                for brks in setBreaks:
                    for brk in brks:
                        brk.line = brk.line + linesAdded
                        # merge in moved breaks
                        if bpList.has_key((filename, brk.line)):
                            bpList[filename, brk.line].append(brk)
                        else:
                            bplist[filename, brk.line] = [brk]

    def OnSaveBreakPoints(self, event):
        self.saveBreakpoints()

    def OnLoadBreakPoints(self, event):
        self.tryLoadBreakpoints()

    def OnSetBreakPoint(self, event):
        line = self.LineFromPosition(self.GetCurrentPos()) + 1
        if self.breaks.hasBreakpoint(line):
            self.deleteBreakPoint(line)
        else:
            self.addBreakPoint(line)

    def OnMarginClick(self, event):
        lineClicked = self.LineFromPosition(event.GetPosition()) + 1
        if self.breaks.hasBreakpoint(lineClicked):
            self.deleteBreakPoint(lineClicked)
        else:
            self.addBreakPoint(lineClicked)


#---Language mixins-------------------------------------------------------------
class LanguageSTCMix:
    def __init__(self, wId, marginNumWidth, language, config):
        self.language = language
        (cfg, self.commonDefs, self.styleIdNames, self.styles, psgn, psg, olsgn,
              olsg, ds, self.lexer, self.keywords, bi) = \
              self.getSTCStyles(config, language)

        #self.SetEOLMode(wx.stc.STC_EOL_LF)
        #self.eol = '\n'
        self.SetBufferedDraw(Preferences.STCBufferedDraw)
        self.SetCaretPeriod(Preferences.STCCaretPeriod)

        self.SetViewWhiteSpace(Preferences.STCViewWhiteSpace)
        self.SetViewEOL(Preferences.STCViewEOL)

        self.SetIndent(Preferences.STCIndent)
        self.SetUseTabs(Preferences.STCUseTabs)
        self.SetTabWidth(Preferences.STCTabWidth)
        self.SetCaretPeriod(Preferences.STCCaretPeriod)
        #if Preferences.STCCaretPolicy:
        #    self.SetCaretPolicy(Preferences.STCCaretPolicy,
        #                        Preferences.STCCaretPolicySlop)

        self.SetEdgeMode(Preferences.STCEdgeMode)
        self.SetEdgeColumn(Preferences.STCEdgeColumnWidth)

        if marginNumWidth:
            self.SetMargins(1, 1)
            self.SetMarginType(marginNumWidth[0], wx.stc.STC_MARGIN_NUMBER)
            self.SetMarginWidth(marginNumWidth[0], marginNumWidth[1])

        self.Bind(wx.stc.EVT_STC_UPDATEUI, self.OnUpdateUI, id=wId)

    def setStyles(self, commonOverride=None):
        commonDefs = {}
        commonDefs.update(self.commonDefs)
        if commonOverride is not None:
            commonDefs.update(commonOverride)

        STCStyleEditor.setSTCStyles(self, self.styles, self.styleIdNames,
              commonDefs, self.language, self.lexer, self.keywords)

    def getSTCStyles(self, config, language):
        """ Override to set values directly """
        return STCStyleEditor.initFromConfig(config, language)


    keymap={'euro': {226: chr(124), 69: chr(128), 81: chr(64), 77: chr(181),
                     48: chr(125), 337: chr(126), 50: chr(178), 51: chr(179),
                     55: chr(123), 56: chr(91), 57: chr(93), 219: chr(92),
                    },
            'swiss-german': {192: chr(93), 226: chr(92), 50: chr(64),
                             51: chr(35), 55: chr(124), 186: chr(91),
                             219: chr(96), 220: chr(123), 221: chr(126),
                             223: chr(125),
                            },
            'italian': {192: chr(64), 337: chr(93), 186: chr(91), 219: chr(123),
                        221: chr(125), 222: chr(35),
                       },
            'france': {226: chr(54), 48: chr(64), 337: chr(125), 50: chr(126),
                       51: chr(35), 52: chr(123), 53: chr(91), 54: chr(124),
                       55: chr(96), 56: chr(92), 219: chr(93),
                      },
           }
    def handleSpecialEuropeanKeys(self, event, countryKeymap='euro'):
        key = event.GetKeyCode()
        keymap = self.keymap[countryKeymap]
        if event.AltDown() and event.ControlDown() and keymap.has_key(key):
            currPos = self.GetCurrentPos()
            self.InsertText(currPos, keymap[key])
            self.SetCurrentPos(self.GetCurrentPos()+1)
            self.SetSelectionStart(self.GetCurrentPos())


stcConfigPath = os.path.join(Preferences.rcPath, 'stc-styles.rc.cfg')

class PythonStyledTextCtrlMix(LanguageSTCMix):
    def __init__(self, wId, margin):
        LanguageSTCMix.__init__(self, wId, margin, 'python', stcConfigPath)

        # XXX add 'with'. here or in stc config?
        self.keywords += ' yield None True False'

        self.setStyles()

    def grayout(self, do):
        if not Preferences.grayoutSource: return
        if do: f = {'backcol': '#EEF2FF'}
        else: f = None
        self.setStyles(f)

    def OnUpdateUI(self, event):
        # check for matching braces
        braceAtCaret = -1
        braceOpposite = -1
        charBefore = None
        caretPos = self.GetCurrentPos()
        if caretPos > 0:
            try:
                charBefore = chr(self.GetCharAt(caretPos - 1))
            except ValueError:
                charBefore = ''
            styleBefore = self.GetStyleAt(caretPos - 1)

        # check before
        if charBefore and charBefore in "[]{}()" and styleBefore == 10:
            braceAtCaret = caretPos - 1

        # check after
        if braceAtCaret < 0:
            try:
                charAfter = chr(self.GetCharAt(caretPos))
            except ValueError:
                charAfter = ''
            styleAfter = self.GetStyleAt(caretPos)

            if charAfter and charAfter in "[]{}()" and styleAfter == 10:
                braceAtCaret = caretPos

        if braceAtCaret >= 0:
            braceOpposite = self.BraceMatch(braceAtCaret)

        if braceAtCaret != -1 and braceOpposite == -1:
            self.BraceBadLight(braceAtCaret)
        else:
            self.BraceHighlight(braceAtCaret, braceOpposite)
            # self.Refresh(False)

    def doAutoIndent(self, prevline, pos):
        stripprevline = prevline.strip()
        if stripprevline:
            indent = prevline[:prevline.find(stripprevline)]
        else:
            try:
                indent = prevline.strip('\r\n', 1)
            except TypeError:
                indent = prevline
                while indent and indent[-1] in ('\r', '\n'):
                    indent = indent[:-1]

        if self.GetUseTabs():
            indtBlock = '\t'
        else:
            # XXX Why did I do this?
            indtBlock = self.GetTabWidth()*' '

        if _is_block_opener(prevline):
            indent = indent + indtBlock
        elif _is_block_closer(prevline):
            indent = indent[:-1*len(indtBlock)]

        self.BeginUndoAction()
        try:
            self.InsertText(pos, indent)
            self.GotoPos(pos + len(indent))
        finally:
            self.EndUndoAction()

class TextSTCMix(LanguageSTCMix):
    def __init__(self, wId):
        LanguageSTCMix.__init__(self, wId, (), 'text', stcConfigPath)
        self.setStyles()

## 1 :
## 2 : diff
## 3 : +++/---
## 4 : @
## 5 : -
## 6 : +
class DiffSTCMix(LanguageSTCMix):
    def __init__(self, wId):
        LanguageSTCMix.__init__(self, wId, (), 'diff', stcConfigPath)
        self.setStyles()

from types import IntType, SliceType, StringType
class STCLinesList:
    def __init__(self, STC):
        self.__STC = STC

    def _rememberPos(self):
        self.__pos = self.GetCurrentPos()

    def __getitem__(self, key):
        if type(key) is IntType:
            # XXX last char is garbage
            if key < len(self):
                return self.__STC.GetLine(key)
            else:
                raise IndexError
        elif type(key) is SliceType:
            res = []
            for idx in range(key.start, key.stop):
                res.append(self[idx])
            return res
        else:
            raise TypeError, _('%s not supported') % `type(key)`

    def __setitem__(self, key, value):
        stc = self.__STC
        if type(key) is IntType:
            assert type(value) is StringType
            if key < len(self):
                stc.SetSelection(stc.PositionFromLine(key), stc.GetLineEndPosition(key))
                stc.ReplaceSelection(value)
            else:
                raise IndexError
        elif type(key) is SliceType:
            lines = eols[stc.GetEOLMode()].join(value)
            stc.SetSelection(stc.PositionFromLine(key.start),
                  stc.GetLineEndPosition(key.stop))
            stc.ReplaceSelection(lines)
        else:
            raise TypeError, _('%s not supported') % `type(key)`

    def __delitem__(self, key):
        stc = self.__STC
        if type(key) is IntType:
            stc.SetSelection(stc.PositionFromLine(key), stc.GetLineEndPosition(key)+1)
            stc.ReplaceSelection('')
        elif type(key) is SliceType:
            stc.SetSelection(stc.PositionFromLine(key.start),
                  stc.GetLineEndPosition(key.stop)+1)
            stc.ReplaceSelection('')
        else:
            raise TypeError, _('%s not supported') % `type(key)`

    def __getattr__(self, name):
        if name == 'current':
            return self.__STC.GetCurrentLine()
        if name == 'count':
            return self.__STC.GetLineCount()
        if name == 'size':
            return self.__STC.GetTextLength()
        # dubious
        if name == 'line':
            return self.__STC.GetCurLine()[0]
        # dubious
        if name == 'pos':
            return self.__STC.GetCurrentPos()

        raise AttributeError, name

    def __len__(self):
        return self.__STC.GetLineCount()
