# Does Python source formatting for Scintilla controls.
import array
import string

import win32api
import win32con
import win32ui

from . import scintillacon

WM_KICKIDLE = 0x036A

# Used to indicate that style should use default color
from win32con import CLR_INVALID

debugging = 0
if debugging:
    # Output must go to another process else the result of
    # the printing itself will trigger again trigger a trace.

    import win32trace
    import win32traceutil

    def trace(*args):
        win32trace.write(" ".join(map(str, args)) + "\n")

else:
    trace = lambda *args: None


class Style:
    """Represents a single format"""

    def __init__(self, name, format, background=CLR_INVALID):
        self.name = name  # Name the format represents eg, "String", "Class"
        # Default background for each style is only used when there are no
        # saved settings (generally on first startup)
        self.background = self.default_background = background
        if isinstance(format, str):
            self.aliased = format
            self.format = None
        else:
            self.format = format
            self.aliased = None
        self.stylenum = None  # Not yet registered.

    def IsBasedOnDefault(self):
        return len(self.format) == 5

    # If the currently extended font definition matches the
    # default format, restore the format to the "simple" format.
    def NormalizeAgainstDefault(self, defaultFormat):
        if self.IsBasedOnDefault():
            return 0  # No more to do, and not changed.
        bIsDefault = (
            self.format[7] == defaultFormat[7] and self.format[2] == defaultFormat[2]
        )
        if bIsDefault:
            self.ForceAgainstDefault()
        return bIsDefault

    def ForceAgainstDefault(self):
        self.format = self.format[:5]

    def GetCompleteFormat(self, defaultFormat):
        # Get the complete style after applying any relevant defaults.
        if len(self.format) == 5:  # It is a default one
            fmt = self.format + defaultFormat[5:]
        else:
            fmt = self.format
        flags = (
            win32con.CFM_BOLD
            | win32con.CFM_CHARSET
            | win32con.CFM_COLOR
            | win32con.CFM_FACE
            | win32con.CFM_ITALIC
            | win32con.CFM_SIZE
        )
        return (flags,) + fmt[1:]


# The Formatter interface
# used primarily when the actual formatting is done by Scintilla!
class FormatterBase:
    def __init__(self, scintilla):
        self.scintilla = scintilla
        self.baseFormatFixed = (-402653169, 0, 200, 0, 0, 0, 49, "Courier New")
        self.baseFormatProp = (-402653169, 0, 200, 0, 0, 0, 49, "Arial")
        self.bUseFixed = 1
        self.styles = {}  # Indexed by name
        self.styles_by_id = {}  # Indexed by allocated ID.
        self.SetStyles()

    def HookFormatter(self, parent=None):
        raise NotImplementedError

    # Used by the IDLE extensions to quickly determine if a character is a string.
    def GetStringStyle(self, pos):
        try:
            style = self.styles_by_id[self.scintilla.SCIGetStyleAt(pos)]
        except KeyError:
            # A style we don't know about - probably not even a .py file - can't be a string
            return None
        if style.name in self.string_style_names:
            return style
        return None

    def RegisterStyle(self, style, stylenum):
        assert stylenum is not None, "We must have a style number"
        assert style.stylenum is None, "Style has already been registered"
        assert stylenum not in self.styles, "We are reusing a style number!"
        style.stylenum = stylenum
        self.styles[style.name] = style
        self.styles_by_id[stylenum] = style

    def SetStyles(self):
        raise NotImplementedError

    def GetSampleText(self):
        return "Sample Text for the Format Dialog"

    def GetDefaultFormat(self):
        if self.bUseFixed:
            return self.baseFormatFixed
        return self.baseFormatProp

    # Update the control with the new style format.
    def _ReformatStyle(self, style):
        ## Selection (background only for now)
        ## Passing False for WPARAM to SCI_SETSELBACK is documented as resetting to scintilla default,
        ## but does not work - selection background is not visible at all.
        ## Default value in SPECIAL_STYLES taken from scintilla source.
        if style.name == STYLE_SELECTION:
            clr = style.background
            self.scintilla.SendScintilla(scintillacon.SCI_SETSELBACK, True, clr)

            ## Can't change font for selection, but could set color
            ## However, the font color dropbox has no option for default, and thus would
            ## always override syntax coloring
            ## clr = style.format[4]
            ## self.scintilla.SendScintilla(scintillacon.SCI_SETSELFORE, clr != CLR_INVALID, clr)
            return

        assert style.stylenum is not None, "Unregistered style."
        # print("Reformat style", style.name, style.stylenum)
        scintilla = self.scintilla
        stylenum = style.stylenum
        # Now we have the style number, indirect for the actual style.
        if style.aliased is not None:
            style = self.styles[style.aliased]
        f = style.format
        if style.IsBasedOnDefault():
            baseFormat = self.GetDefaultFormat()
        else:
            baseFormat = f
        scintilla.SCIStyleSetFore(stylenum, f[4])
        scintilla.SCIStyleSetFont(stylenum, baseFormat[7], baseFormat[5])
        if f[1] & 1:
            scintilla.SCIStyleSetBold(stylenum, 1)
        else:
            scintilla.SCIStyleSetBold(stylenum, 0)
        if f[1] & 2:
            scintilla.SCIStyleSetItalic(stylenum, 1)
        else:
            scintilla.SCIStyleSetItalic(stylenum, 0)
        scintilla.SCIStyleSetSize(stylenum, int(baseFormat[2] / 20))
        scintilla.SCIStyleSetEOLFilled(stylenum, 1)  # Only needed for unclosed strings.

        ## Default style background to whitespace background if set,
        ##	otherwise use system window color
        bg = style.background
        if bg == CLR_INVALID:
            bg = self.styles[STYLE_DEFAULT].background
        if bg == CLR_INVALID:
            bg = win32api.GetSysColor(win32con.COLOR_WINDOW)
        scintilla.SCIStyleSetBack(stylenum, bg)

    def GetStyleByNum(self, stylenum):
        return self.styles_by_id[stylenum]

    def ApplyFormattingStyles(self, bReload=1):
        if bReload:
            self.LoadPreferences()
        baseFormat = self.GetDefaultFormat()
        defaultStyle = Style("default", baseFormat)
        defaultStyle.stylenum = scintillacon.STYLE_DEFAULT
        self._ReformatStyle(defaultStyle)
        for style in self.styles.values():
            if style.aliased is None:
                style.NormalizeAgainstDefault(baseFormat)
            self._ReformatStyle(style)
        self.scintilla.InvalidateRect()

    # Some functions for loading and saving preferences.  By default
    # an INI file (well, MFC maps this to the registry) is used.
    def LoadPreferences(self):
        self.baseFormatFixed = eval(
            self.LoadPreference("Base Format Fixed", str(self.baseFormatFixed))
        )
        self.baseFormatProp = eval(
            self.LoadPreference("Base Format Proportional", str(self.baseFormatProp))
        )
        self.bUseFixed = int(self.LoadPreference("Use Fixed", 1))

        for style in self.styles.values():
            new = self.LoadPreference(style.name, str(style.format))
            try:
                style.format = eval(new)
            except:
                print("Error loading style data for", style.name)
            # Use "vanilla" background hardcoded in PYTHON_STYLES if no settings in registry
            style.background = int(
                self.LoadPreference(
                    style.name + " background", style.default_background
                )
            )

    def LoadPreference(self, name, default):
        return win32ui.GetProfileVal("Format", name, default)

    def SavePreferences(self):
        self.SavePreference("Base Format Fixed", str(self.baseFormatFixed))
        self.SavePreference("Base Format Proportional", str(self.baseFormatProp))
        self.SavePreference("Use Fixed", self.bUseFixed)
        for style in self.styles.values():
            if style.aliased is None:
                self.SavePreference(style.name, str(style.format))
                bg_name = style.name + " background"
                self.SavePreference(bg_name, style.background)

    def SavePreference(self, name, value):
        win32ui.WriteProfileVal("Format", name, value)


# An abstract formatter
# For all formatters we actually implement here.
# (as opposed to those formatters built in to Scintilla)
class Formatter(FormatterBase):
    def __init__(self, scintilla):
        self.bCompleteWhileIdle = 0
        self.bHaveIdleHandler = 0  # Don't currently have an idle handle
        self.nextstylenum = 0
        FormatterBase.__init__(self, scintilla)

    def HookFormatter(self, parent=None):
        if parent is None:
            parent = self.scintilla.GetParent()  # was GetParentFrame()!?
        parent.HookNotify(self.OnStyleNeeded, scintillacon.SCN_STYLENEEDED)

    def OnStyleNeeded(self, std, extra):
        notify = self.scintilla.SCIUnpackNotifyMessage(extra)
        endStyledChar = self.scintilla.SendScintilla(scintillacon.SCI_GETENDSTYLED)
        lineEndStyled = self.scintilla.LineFromChar(endStyledChar)
        endStyled = self.scintilla.LineIndex(lineEndStyled)
        # print(
        #     "endPosPaint",
        #     endPosPaint,
        #     "endStyledChar",
        #     endStyledChar,
        #     "lineEndStyled",
        #     lineEndStyled,
        #     "endStyled",
        #     endStyled,
        # )
        self.Colorize(endStyled, notify.position)

    def ColorSeg(self, start, end, styleName):
        end += 1
        # 		assert end-start>=0, "Can't have negative styling"
        stylenum = self.styles[styleName].stylenum
        while start < end:
            self.style_buffer[start] = stylenum
            start += 1
        # self.scintilla.SCISetStyling(end - start + 1, stylenum)

    def RegisterStyle(self, style, stylenum=None):
        if stylenum is None:
            stylenum = self.nextstylenum
            self.nextstylenum += 1
        FormatterBase.RegisterStyle(self, style, stylenum)

    def ColorizeString(self, str, styleStart):
        raise RuntimeError("You must override this method")

    def Colorize(self, start=0, end=-1):
        scintilla = self.scintilla
        # scintilla's formatting is all done in terms of utf, so
        # we work with utf8 bytes instead of unicode.  This magically
        # works as any extended chars found in the utf8 don't change
        # the semantics.
        stringVal = scintilla.GetTextRange(start, end, decode=False)
        if start > 0:
            stylenum = scintilla.SCIGetStyleAt(start - 1)
            styleStart = self.GetStyleByNum(stylenum).name
        else:
            styleStart = None
        # 		trace("Coloring", start, end, end-start, len(stringVal), styleStart, self.scintilla.SCIGetCharAt(start))
        scintilla.SCIStartStyling(start, 31)
        self.style_buffer = array.array("b", (0,) * len(stringVal))
        self.ColorizeString(stringVal, styleStart)
        scintilla.SCISetStylingEx(self.style_buffer)
        self.style_buffer = None
        # 		trace("After styling, end styled is", self.scintilla.SCIGetEndStyled())
        if (
            self.bCompleteWhileIdle
            and not self.bHaveIdleHandler
            and end != -1
            and end < scintilla.GetTextLength()
        ):
            self.bHaveIdleHandler = 1
            win32ui.GetApp().AddIdleHandler(self.DoMoreColoring)
            # Kicking idle makes the app seem slower when initially repainting!

    # 			win32ui.GetMainFrame().PostMessage(WM_KICKIDLE, 0, 0)

    def DoMoreColoring(self, handler, count):
        try:
            scintilla = self.scintilla
            endStyled = scintilla.SCIGetEndStyled()
            lineStartStyled = scintilla.LineFromChar(endStyled)
            start = scintilla.LineIndex(lineStartStyled)
            end = scintilla.LineIndex(lineStartStyled + 1)
            textlen = scintilla.GetTextLength()
            if end < 0:
                end = textlen

            finished = end >= textlen
            self.Colorize(start, end)
        except (win32ui.error, AttributeError):
            # Window may have closed before we finished - no big deal!
            finished = 1

        if finished:
            self.bHaveIdleHandler = 0
            win32ui.GetApp().DeleteIdleHandler(handler)
        return not finished


# A Formatter that knows how to format Python source
from keyword import iskeyword, kwlist

wordstarts = "_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
wordchars = "._0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
operators = "%^&*()-+=|{}[]:;<>,/?!.~"

STYLE_DEFAULT = "Whitespace"
STYLE_COMMENT = "Comment"
STYLE_COMMENT_BLOCK = "Comment Blocks"
STYLE_NUMBER = "Number"
STYLE_STRING = "String"
STYLE_SQSTRING = "SQ String"
STYLE_TQSSTRING = "TQS String"
STYLE_TQDSTRING = "TQD String"
STYLE_KEYWORD = "Keyword"
STYLE_CLASS = "Class"
STYLE_METHOD = "Method"
STYLE_OPERATOR = "Operator"
STYLE_IDENTIFIER = "Identifier"
STYLE_BRACE = "Brace/Paren - matching"
STYLE_BRACEBAD = "Brace/Paren - unmatched"
STYLE_STRINGEOL = "String with no terminator"
STYLE_LINENUMBER = "Line numbers"
STYLE_INDENTGUIDE = "Indent guide"
STYLE_SELECTION = "Selection"

STRING_STYLES = [
    STYLE_STRING,
    STYLE_SQSTRING,
    STYLE_TQSSTRING,
    STYLE_TQDSTRING,
    STYLE_STRINGEOL,
]

# These styles can have any ID - they are not special to scintilla itself.
# However, if we use the built-in lexer, then we must use its style numbers
# so in that case, they _are_ special.
# (name, format, background, scintilla id)
PYTHON_STYLES = [
    (STYLE_DEFAULT, (0, 0, 200, 0, 0x808080), CLR_INVALID, scintillacon.SCE_P_DEFAULT),
    (
        STYLE_COMMENT,
        (0, 2, 200, 0, 0x008000),
        CLR_INVALID,
        scintillacon.SCE_P_COMMENTLINE,
    ),
    (
        STYLE_COMMENT_BLOCK,
        (0, 2, 200, 0, 0x808080),
        CLR_INVALID,
        scintillacon.SCE_P_COMMENTBLOCK,
    ),
    (STYLE_NUMBER, (0, 0, 200, 0, 0x808000), CLR_INVALID, scintillacon.SCE_P_NUMBER),
    (STYLE_STRING, (0, 0, 200, 0, 0x008080), CLR_INVALID, scintillacon.SCE_P_STRING),
    (STYLE_SQSTRING, STYLE_STRING, CLR_INVALID, scintillacon.SCE_P_CHARACTER),
    (STYLE_TQSSTRING, STYLE_STRING, CLR_INVALID, scintillacon.SCE_P_TRIPLE),
    (STYLE_TQDSTRING, STYLE_STRING, CLR_INVALID, scintillacon.SCE_P_TRIPLEDOUBLE),
    (STYLE_STRINGEOL, (0, 0, 200, 0, 0x000000), 0x008080, scintillacon.SCE_P_STRINGEOL),
    (STYLE_KEYWORD, (0, 1, 200, 0, 0x800000), CLR_INVALID, scintillacon.SCE_P_WORD),
    (STYLE_CLASS, (0, 1, 200, 0, 0xFF0000), CLR_INVALID, scintillacon.SCE_P_CLASSNAME),
    (STYLE_METHOD, (0, 1, 200, 0, 0x808000), CLR_INVALID, scintillacon.SCE_P_DEFNAME),
    (
        STYLE_OPERATOR,
        (0, 0, 200, 0, 0x000000),
        CLR_INVALID,
        scintillacon.SCE_P_OPERATOR,
    ),
    (
        STYLE_IDENTIFIER,
        (0, 0, 200, 0, 0x000000),
        CLR_INVALID,
        scintillacon.SCE_P_IDENTIFIER,
    ),
]

# These styles _always_ have this specific style number, regardless of
# internal or external formatter.
SPECIAL_STYLES = [
    (STYLE_BRACE, (0, 0, 200, 0, 0x000000), 0xFFFF80, scintillacon.STYLE_BRACELIGHT),
    (STYLE_BRACEBAD, (0, 0, 200, 0, 0x000000), 0x8EA5F2, scintillacon.STYLE_BRACEBAD),
    (
        STYLE_LINENUMBER,
        (0, 0, 200, 0, 0x000000),
        win32api.GetSysColor(win32con.COLOR_3DFACE),
        scintillacon.STYLE_LINENUMBER,
    ),
    (
        STYLE_INDENTGUIDE,
        (0, 0, 200, 0, 0x000000),
        CLR_INVALID,
        scintillacon.STYLE_INDENTGUIDE,
    ),
    ## Not actually a style; requires special handling to send appropriate messages to scintilla
    (
        STYLE_SELECTION,
        (0, 0, 200, 0, CLR_INVALID),
        win32api.RGB(0xC0, 0xC0, 0xC0),
        999999,
    ),
]

PythonSampleCode = """\
# Some Python
class Sample(Super):
  def Fn(self):
\tself.v = 1024
dest = 'dest.html'
x = func(a + 1)|)
s = "I forget...
## A large
## comment block"""


class PythonSourceFormatter(Formatter):
    string_style_names = STRING_STYLES

    def GetSampleText(self):
        return PythonSampleCode

    def LoadStyles(self):
        pass

    def SetStyles(self):
        for name, format, bg, ignore in PYTHON_STYLES:
            self.RegisterStyle(Style(name, format, bg))
        for name, format, bg, sc_id in SPECIAL_STYLES:
            self.RegisterStyle(Style(name, format, bg), sc_id)

    def ClassifyWord(self, cdoc, start, end, prevWord):
        word = cdoc[start : end + 1].decode("latin-1")
        attr = STYLE_IDENTIFIER
        if prevWord == "class":
            attr = STYLE_CLASS
        elif prevWord == "def":
            attr = STYLE_METHOD
        elif word[0] in string.digits:
            attr = STYLE_NUMBER
        elif iskeyword(word):
            attr = STYLE_KEYWORD
        self.ColorSeg(start, end, attr)
        return word

    def ColorizeString(self, str, styleStart):
        if styleStart is None:
            styleStart = STYLE_DEFAULT
        return self.ColorizePythonCode(str, 0, styleStart)

    def ColorizePythonCode(self, cdoc, charStart, styleStart):
        # Straight translation of C++, should do better
        lengthDoc = len(cdoc)
        if lengthDoc <= charStart:
            return
        prevWord = ""
        state = styleStart
        chPrev = chPrev2 = chPrev3 = " "
        chNext2 = chNext = cdoc[charStart : charStart + 1].decode("latin-1")
        startSeg = i = charStart
        while i < lengthDoc:
            ch = chNext
            chNext = " "
            if i + 1 < lengthDoc:
                chNext = cdoc[i + 1 : i + 2].decode("latin-1")
            chNext2 = " "
            if i + 2 < lengthDoc:
                chNext2 = cdoc[i + 2 : i + 3].decode("latin-1")
            if state == STYLE_DEFAULT:
                if ch in wordstarts:
                    self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
                    state = STYLE_KEYWORD
                    startSeg = i
                elif ch == "#":
                    self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
                    if chNext == "#":
                        state = STYLE_COMMENT_BLOCK
                    else:
                        state = STYLE_COMMENT
                    startSeg = i
                elif ch == '"':
                    self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
                    startSeg = i
                    state = STYLE_COMMENT
                    if chNext == '"' and chNext2 == '"':
                        i += 2
                        state = STYLE_TQDSTRING
                        ch = " "
                        chPrev = " "
                        chNext = " "
                        if i + 1 < lengthDoc:
                            chNext = cdoc[i + 1]
                    else:
                        state = STYLE_STRING
                elif ch == "'":
                    self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
                    startSeg = i
                    state = STYLE_COMMENT
                    if chNext == "'" and chNext2 == "'":
                        i += 2
                        state = STYLE_TQSSTRING
                        ch = " "
                        chPrev = " "
                        chNext = " "
                        if i + 1 < lengthDoc:
                            chNext = cdoc[i + 1]
                    else:
                        state = STYLE_SQSTRING
                elif ch in operators:
                    self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
                    self.ColorSeg(i, i, STYLE_OPERATOR)
                    startSeg = i + 1
            elif state == STYLE_KEYWORD:
                if ch not in wordchars:
                    prevWord = self.ClassifyWord(cdoc, startSeg, i - 1, prevWord)
                    state = STYLE_DEFAULT
                    startSeg = i
                    if ch == "#":
                        if chNext == "#":
                            state = STYLE_COMMENT_BLOCK
                        else:
                            state = STYLE_COMMENT
                    elif ch == '"':
                        if chNext == '"' and chNext2 == '"':
                            i += 2
                            state = STYLE_TQDSTRING
                            ch = " "
                            chPrev = " "
                            chNext = " "
                            if i + 1 < lengthDoc:
                                chNext = cdoc[i + 1]
                        else:
                            state = STYLE_STRING
                    elif ch == "'":
                        if chNext == "'" and chNext2 == "'":
                            i += 2
                            state = STYLE_TQSSTRING
                            ch = " "
                            chPrev = " "
                            chNext = " "
                            if i + 1 < lengthDoc:
                                chNext = cdoc[i + 1]
                        else:
                            state = STYLE_SQSTRING
                    elif ch in operators:
                        self.ColorSeg(startSeg, i, STYLE_OPERATOR)
                        startSeg = i + 1
            elif state == STYLE_COMMENT or state == STYLE_COMMENT_BLOCK:
                if ch == "\r" or ch == "\n":
                    self.ColorSeg(startSeg, i - 1, state)
                    state = STYLE_DEFAULT
                    startSeg = i
            elif state == STYLE_STRING:
                if ch == "\\":
                    if chNext == '"' or chNext == "'" or chNext == "\\":
                        i += 1
                        ch = chNext
                        chNext = " "
                        if i + 1 < lengthDoc:
                            chNext = cdoc[i + 1]
                elif ch == '"':
                    self.ColorSeg(startSeg, i, STYLE_STRING)
                    state = STYLE_DEFAULT
                    startSeg = i + 1
            elif state == STYLE_SQSTRING:
                if ch == "\\":
                    if chNext == '"' or chNext == "'" or chNext == "\\":
                        i += 1
                        ch = chNext
                        chNext = " "
                        if i + 1 < lengthDoc:
                            chNext = cdoc[i + 1]
                elif ch == "'":
                    self.ColorSeg(startSeg, i, STYLE_SQSTRING)
                    state = STYLE_DEFAULT
                    startSeg = i + 1
            elif state == STYLE_TQSSTRING:
                if ch == "'" and chPrev == "'" and chPrev2 == "'" and chPrev3 != "\\":
                    self.ColorSeg(startSeg, i, STYLE_TQSSTRING)
                    state = STYLE_DEFAULT
                    startSeg = i + 1
            elif (
                state == STYLE_TQDSTRING
                and ch == '"'
                and chPrev == '"'
                and chPrev2 == '"'
                and chPrev3 != "\\"
            ):
                self.ColorSeg(startSeg, i, STYLE_TQDSTRING)
                state = STYLE_DEFAULT
                startSeg = i + 1
            chPrev3 = chPrev2
            chPrev2 = chPrev
            chPrev = ch
            i += 1
        if startSeg < lengthDoc:
            if state == STYLE_KEYWORD:
                self.ClassifyWord(cdoc, startSeg, lengthDoc - 1, prevWord)
            else:
                self.ColorSeg(startSeg, lengthDoc - 1, state)


# These taken from the SciTE properties file.
source_formatter_extensions = [
    (".py .pys .pyw".split(), scintillacon.SCLEX_PYTHON),
    (".html .htm .asp .shtml".split(), scintillacon.SCLEX_HTML),
    (
        "c .cc .cpp .cxx .h .hh .hpp .hxx .idl .odl .php3 .phtml .inc .js".split(),
        scintillacon.SCLEX_CPP,
    ),
    (".vbs .frm .ctl .cls".split(), scintillacon.SCLEX_VB),
    (".pl .pm .cgi .pod".split(), scintillacon.SCLEX_PERL),
    (".sql .spec .body .sps .spb .sf .sp".split(), scintillacon.SCLEX_SQL),
    (".tex .sty".split(), scintillacon.SCLEX_LATEX),
    (".xml .xul".split(), scintillacon.SCLEX_XML),
    (".err".split(), scintillacon.SCLEX_ERRORLIST),
    (".mak".split(), scintillacon.SCLEX_MAKEFILE),
    (".bat .cmd".split(), scintillacon.SCLEX_BATCH),
]


class BuiltinSourceFormatter(FormatterBase):
    # A class that represents a formatter built-in to Scintilla
    def __init__(self, scintilla, ext):
        self.ext = ext
        FormatterBase.__init__(self, scintilla)

    def Colorize(self, start=0, end=-1):
        self.scintilla.SendScintilla(scintillacon.SCI_COLOURISE, start, end)

    def RegisterStyle(self, style, stylenum=None):
        assert style.stylenum is None, "Style has already been registered"
        if stylenum is None:
            stylenum = self.nextstylenum
            self.nextstylenum += 1
        assert self.styles.get(stylenum) is None, "We are reusing a style number!"
        style.stylenum = stylenum
        self.styles[style.name] = style
        self.styles_by_id[stylenum] = style

    def HookFormatter(self, parent=None):
        sc = self.scintilla
        for exts, formatter in source_formatter_extensions:
            if self.ext in exts:
                formatter_use = formatter
                break
        else:
            formatter_use = scintillacon.SCLEX_PYTHON
        sc.SendScintilla(scintillacon.SCI_SETLEXER, formatter_use)
        keywords = " ".join(kwlist)
        sc.SCISetKeywords(keywords)


class BuiltinPythonSourceFormatter(BuiltinSourceFormatter):
    sci_lexer_name = scintillacon.SCLEX_PYTHON
    string_style_names = STRING_STYLES

    def __init__(self, sc, ext=".py"):
        BuiltinSourceFormatter.__init__(self, sc, ext)

    def SetStyles(self):
        for name, format, bg, sc_id in PYTHON_STYLES:
            self.RegisterStyle(Style(name, format, bg), sc_id)
        for name, format, bg, sc_id in SPECIAL_STYLES:
            self.RegisterStyle(Style(name, format, bg), sc_id)

    def GetSampleText(self):
        return PythonSampleCode
