# Does Python source formatting for Scintilla controls.
import win32ui
import win32api
import win32con
import winerror
import string
import array
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 sys, win32traceutil, win32trace 
	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 representes 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 type(format)==type(''):
			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 defintion 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 dont 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 # Dont 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 "enPosPaint %d endStyledChar %d lineEndStyled %d endStyled %d" % (endPosPaint, endStyledChar, lineEndStyled, endStyled)
		self.Colorize(endStyled, notify.position)

	def ColorSeg(self, start, end, styleName):
		end = 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 = start+1
		#self.scintilla.SCISetStyling(end - start + 1, stylenum)

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

	def ColorizeString(self, str, charStart, 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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
