formatter.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. # Does Python source formatting for Scintilla controls.
  2. import win32ui
  3. import win32api
  4. import win32con
  5. import winerror
  6. import string
  7. import array
  8. from . import scintillacon
  9. WM_KICKIDLE = 0x036A
  10. # Used to indicate that style should use default color
  11. from win32con import CLR_INVALID
  12. debugging = 0
  13. if debugging:
  14. # Output must go to another process else the result of
  15. # the printing itself will trigger again trigger a trace.
  16. import sys, win32traceutil, win32trace
  17. def trace(*args):
  18. win32trace.write(' '.join(map(str, args)) + "\n")
  19. else:
  20. trace = lambda *args: None
  21. class Style:
  22. """Represents a single format
  23. """
  24. def __init__(self, name, format, background = CLR_INVALID):
  25. self.name = name # Name the format representes eg, "String", "Class"
  26. # Default background for each style is only used when there are no
  27. # saved settings (generally on first startup)
  28. self.background = self.default_background = background
  29. if type(format)==type(''):
  30. self.aliased = format
  31. self.format = None
  32. else:
  33. self.format = format
  34. self.aliased = None
  35. self.stylenum = None # Not yet registered.
  36. def IsBasedOnDefault(self):
  37. return len(self.format)==5
  38. # If the currently extended font defintion matches the
  39. # default format, restore the format to the "simple" format.
  40. def NormalizeAgainstDefault(self, defaultFormat):
  41. if self.IsBasedOnDefault():
  42. return 0 # No more to do, and not changed.
  43. bIsDefault = self.format[7] == defaultFormat[7] and \
  44. self.format[2] == defaultFormat[2]
  45. if bIsDefault:
  46. self.ForceAgainstDefault()
  47. return bIsDefault
  48. def ForceAgainstDefault(self):
  49. self.format = self.format[:5]
  50. def GetCompleteFormat(self, defaultFormat):
  51. # Get the complete style after applying any relevant defaults.
  52. if len(self.format)==5: # It is a default one
  53. fmt = self.format + defaultFormat[5:]
  54. else:
  55. fmt = self.format
  56. flags = win32con.CFM_BOLD | win32con.CFM_CHARSET | win32con.CFM_COLOR | win32con.CFM_FACE | win32con.CFM_ITALIC | win32con.CFM_SIZE
  57. return (flags,) + fmt[1:]
  58. # The Formatter interface
  59. # used primarily when the actual formatting is done by Scintilla!
  60. class FormatterBase:
  61. def __init__(self, scintilla):
  62. self.scintilla = scintilla
  63. self.baseFormatFixed = (-402653169, 0, 200, 0, 0, 0, 49, 'Courier New')
  64. self.baseFormatProp = (-402653169, 0, 200, 0, 0, 0, 49, 'Arial')
  65. self.bUseFixed = 1
  66. self.styles = {} # Indexed by name
  67. self.styles_by_id = {} # Indexed by allocated ID.
  68. self.SetStyles()
  69. def HookFormatter(self, parent = None):
  70. raise NotImplementedError()
  71. # Used by the IDLE extensions to quickly determine if a character is a string.
  72. def GetStringStyle(self, pos):
  73. try:
  74. style = self.styles_by_id[self.scintilla.SCIGetStyleAt(pos)]
  75. except KeyError:
  76. # A style we dont know about - probably not even a .py file - can't be a string
  77. return None
  78. if style.name in self.string_style_names:
  79. return style
  80. return None
  81. def RegisterStyle(self, style, stylenum):
  82. assert stylenum is not None, "We must have a style number"
  83. assert style.stylenum is None, "Style has already been registered"
  84. assert stylenum not in self.styles, "We are reusing a style number!"
  85. style.stylenum = stylenum
  86. self.styles[style.name] = style
  87. self.styles_by_id[stylenum] = style
  88. def SetStyles(self):
  89. raise NotImplementedError()
  90. def GetSampleText(self):
  91. return "Sample Text for the Format Dialog"
  92. def GetDefaultFormat(self):
  93. if self.bUseFixed:
  94. return self.baseFormatFixed
  95. return self.baseFormatProp
  96. # Update the control with the new style format.
  97. def _ReformatStyle(self, style):
  98. ## Selection (background only for now)
  99. ## Passing False for WPARAM to SCI_SETSELBACK is documented as resetting to scintilla default,
  100. ## but does not work - selection background is not visible at all.
  101. ## Default value in SPECIAL_STYLES taken from scintilla source.
  102. if style.name == STYLE_SELECTION:
  103. clr = style.background
  104. self.scintilla.SendScintilla(scintillacon.SCI_SETSELBACK, True, clr)
  105. ## Can't change font for selection, but could set color
  106. ## However, the font color dropbox has no option for default, and thus would
  107. ## always override syntax coloring
  108. ## clr = style.format[4]
  109. ## self.scintilla.SendScintilla(scintillacon.SCI_SETSELFORE, clr != CLR_INVALID, clr)
  110. return
  111. assert style.stylenum is not None, "Unregistered style."
  112. #print "Reformat style", style.name, style.stylenum
  113. scintilla=self.scintilla
  114. stylenum = style.stylenum
  115. # Now we have the style number, indirect for the actual style.
  116. if style.aliased is not None:
  117. style = self.styles[style.aliased]
  118. f=style.format
  119. if style.IsBasedOnDefault():
  120. baseFormat = self.GetDefaultFormat()
  121. else: baseFormat = f
  122. scintilla.SCIStyleSetFore(stylenum, f[4])
  123. scintilla.SCIStyleSetFont(stylenum, baseFormat[7], baseFormat[5])
  124. if f[1] & 1: scintilla.SCIStyleSetBold(stylenum, 1)
  125. else: scintilla.SCIStyleSetBold(stylenum, 0)
  126. if f[1] & 2: scintilla.SCIStyleSetItalic(stylenum, 1)
  127. else: scintilla.SCIStyleSetItalic(stylenum, 0)
  128. scintilla.SCIStyleSetSize(stylenum, int(baseFormat[2]/20))
  129. scintilla.SCIStyleSetEOLFilled(stylenum, 1) # Only needed for unclosed strings.
  130. ## Default style background to whitespace background if set,
  131. ## otherwise use system window color
  132. bg = style.background
  133. if bg == CLR_INVALID:
  134. bg = self.styles[STYLE_DEFAULT].background
  135. if bg == CLR_INVALID:
  136. bg = win32api.GetSysColor(win32con.COLOR_WINDOW)
  137. scintilla.SCIStyleSetBack(stylenum, bg)
  138. def GetStyleByNum(self, stylenum):
  139. return self.styles_by_id[stylenum]
  140. def ApplyFormattingStyles(self, bReload=1):
  141. if bReload:
  142. self.LoadPreferences()
  143. baseFormat = self.GetDefaultFormat()
  144. defaultStyle = Style("default", baseFormat)
  145. defaultStyle.stylenum = scintillacon.STYLE_DEFAULT
  146. self._ReformatStyle(defaultStyle)
  147. for style in list(self.styles.values()):
  148. if style.aliased is None:
  149. style.NormalizeAgainstDefault(baseFormat)
  150. self._ReformatStyle(style)
  151. self.scintilla.InvalidateRect()
  152. # Some functions for loading and saving preferences. By default
  153. # an INI file (well, MFC maps this to the registry) is used.
  154. def LoadPreferences(self):
  155. self.baseFormatFixed = eval(self.LoadPreference("Base Format Fixed", str(self.baseFormatFixed)))
  156. self.baseFormatProp = eval(self.LoadPreference("Base Format Proportional", str(self.baseFormatProp)))
  157. self.bUseFixed = int(self.LoadPreference("Use Fixed", 1))
  158. for style in list(self.styles.values()):
  159. new = self.LoadPreference(style.name, str(style.format))
  160. try:
  161. style.format = eval(new)
  162. except:
  163. print("Error loading style data for", style.name)
  164. # Use "vanilla" background hardcoded in PYTHON_STYLES if no settings in registry
  165. style.background = int(self.LoadPreference(style.name + " background", style.default_background))
  166. def LoadPreference(self, name, default):
  167. return win32ui.GetProfileVal("Format", name, default)
  168. def SavePreferences(self):
  169. self.SavePreference("Base Format Fixed", str(self.baseFormatFixed))
  170. self.SavePreference("Base Format Proportional", str(self.baseFormatProp))
  171. self.SavePreference("Use Fixed", self.bUseFixed)
  172. for style in list(self.styles.values()):
  173. if style.aliased is None:
  174. self.SavePreference(style.name, str(style.format))
  175. bg_name = style.name + " background"
  176. self.SavePreference(bg_name, style.background)
  177. def SavePreference(self, name, value):
  178. win32ui.WriteProfileVal("Format", name, value)
  179. # An abstract formatter
  180. # For all formatters we actually implement here.
  181. # (as opposed to those formatters built in to Scintilla)
  182. class Formatter(FormatterBase):
  183. def __init__(self, scintilla):
  184. self.bCompleteWhileIdle = 0
  185. self.bHaveIdleHandler = 0 # Dont currently have an idle handle
  186. self.nextstylenum = 0
  187. FormatterBase.__init__(self, scintilla)
  188. def HookFormatter(self, parent = None):
  189. if parent is None: parent = self.scintilla.GetParent() # was GetParentFrame()!?
  190. parent.HookNotify(self.OnStyleNeeded, scintillacon.SCN_STYLENEEDED)
  191. def OnStyleNeeded(self, std, extra):
  192. notify = self.scintilla.SCIUnpackNotifyMessage(extra)
  193. endStyledChar = self.scintilla.SendScintilla(scintillacon.SCI_GETENDSTYLED)
  194. lineEndStyled = self.scintilla.LineFromChar(endStyledChar)
  195. endStyled = self.scintilla.LineIndex(lineEndStyled)
  196. #print "enPosPaint %d endStyledChar %d lineEndStyled %d endStyled %d" % (endPosPaint, endStyledChar, lineEndStyled, endStyled)
  197. self.Colorize(endStyled, notify.position)
  198. def ColorSeg(self, start, end, styleName):
  199. end = end+1
  200. # assert end-start>=0, "Can't have negative styling"
  201. stylenum = self.styles[styleName].stylenum
  202. while start<end:
  203. self.style_buffer[start]=stylenum
  204. start = start+1
  205. #self.scintilla.SCISetStyling(end - start + 1, stylenum)
  206. def RegisterStyle(self, style, stylenum = None):
  207. if stylenum is None:
  208. stylenum = self.nextstylenum
  209. self.nextstylenum = self.nextstylenum + 1
  210. FormatterBase.RegisterStyle(self, style, stylenum)
  211. def ColorizeString(self, str, charStart, styleStart):
  212. raise RuntimeError("You must override this method")
  213. def Colorize(self, start=0, end=-1):
  214. scintilla = self.scintilla
  215. # scintilla's formatting is all done in terms of utf, so
  216. # we work with utf8 bytes instead of unicode. This magically
  217. # works as any extended chars found in the utf8 don't change
  218. # the semantics.
  219. stringVal = scintilla.GetTextRange(start, end, decode=False)
  220. if start > 0:
  221. stylenum = scintilla.SCIGetStyleAt(start - 1)
  222. styleStart = self.GetStyleByNum(stylenum).name
  223. else:
  224. styleStart = None
  225. # trace("Coloring", start, end, end-start, len(stringVal), styleStart, self.scintilla.SCIGetCharAt(start))
  226. scintilla.SCIStartStyling(start, 31)
  227. self.style_buffer = array.array("b", (0,)*len(stringVal))
  228. self.ColorizeString(stringVal, styleStart)
  229. scintilla.SCISetStylingEx(self.style_buffer)
  230. self.style_buffer = None
  231. # trace("After styling, end styled is", self.scintilla.SCIGetEndStyled())
  232. if self.bCompleteWhileIdle and not self.bHaveIdleHandler and end!=-1 and end < scintilla.GetTextLength():
  233. self.bHaveIdleHandler = 1
  234. win32ui.GetApp().AddIdleHandler(self.DoMoreColoring)
  235. # Kicking idle makes the app seem slower when initially repainting!
  236. # win32ui.GetMainFrame().PostMessage(WM_KICKIDLE, 0, 0)
  237. def DoMoreColoring(self, handler, count):
  238. try:
  239. scintilla = self.scintilla
  240. endStyled = scintilla.SCIGetEndStyled()
  241. lineStartStyled = scintilla.LineFromChar(endStyled)
  242. start = scintilla.LineIndex(lineStartStyled)
  243. end = scintilla.LineIndex(lineStartStyled+1)
  244. textlen = scintilla.GetTextLength()
  245. if end < 0: end = textlen
  246. finished = end >= textlen
  247. self.Colorize(start, end)
  248. except (win32ui.error, AttributeError):
  249. # Window may have closed before we finished - no big deal!
  250. finished = 1
  251. if finished:
  252. self.bHaveIdleHandler = 0
  253. win32ui.GetApp().DeleteIdleHandler(handler)
  254. return not finished
  255. # A Formatter that knows how to format Python source
  256. from keyword import iskeyword, kwlist
  257. wordstarts = '_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
  258. wordchars = '._0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
  259. operators = '%^&*()-+=|{}[]:;<>,/?!.~'
  260. STYLE_DEFAULT = "Whitespace"
  261. STYLE_COMMENT = "Comment"
  262. STYLE_COMMENT_BLOCK = "Comment Blocks"
  263. STYLE_NUMBER = "Number"
  264. STYLE_STRING = "String"
  265. STYLE_SQSTRING = "SQ String"
  266. STYLE_TQSSTRING = "TQS String"
  267. STYLE_TQDSTRING = "TQD String"
  268. STYLE_KEYWORD = "Keyword"
  269. STYLE_CLASS = "Class"
  270. STYLE_METHOD = "Method"
  271. STYLE_OPERATOR = "Operator"
  272. STYLE_IDENTIFIER = "Identifier"
  273. STYLE_BRACE = "Brace/Paren - matching"
  274. STYLE_BRACEBAD = "Brace/Paren - unmatched"
  275. STYLE_STRINGEOL = "String with no terminator"
  276. STYLE_LINENUMBER = "Line numbers"
  277. STYLE_INDENTGUIDE = "Indent guide"
  278. STYLE_SELECTION = "Selection"
  279. STRING_STYLES = [STYLE_STRING, STYLE_SQSTRING, STYLE_TQSSTRING, STYLE_TQDSTRING, STYLE_STRINGEOL]
  280. # These styles can have any ID - they are not special to scintilla itself.
  281. # However, if we use the built-in lexer, then we must use its style numbers
  282. # so in that case, they _are_ special.
  283. # (name, format, background, scintilla id)
  284. PYTHON_STYLES = [
  285. (STYLE_DEFAULT, (0, 0, 200, 0, 0x808080), CLR_INVALID, scintillacon.SCE_P_DEFAULT ),
  286. (STYLE_COMMENT, (0, 2, 200, 0, 0x008000), CLR_INVALID, scintillacon.SCE_P_COMMENTLINE ),
  287. (STYLE_COMMENT_BLOCK,(0, 2, 200, 0, 0x808080), CLR_INVALID, scintillacon.SCE_P_COMMENTBLOCK ),
  288. (STYLE_NUMBER, (0, 0, 200, 0, 0x808000), CLR_INVALID, scintillacon.SCE_P_NUMBER ),
  289. (STYLE_STRING, (0, 0, 200, 0, 0x008080), CLR_INVALID, scintillacon.SCE_P_STRING ),
  290. (STYLE_SQSTRING, STYLE_STRING, CLR_INVALID, scintillacon.SCE_P_CHARACTER ),
  291. (STYLE_TQSSTRING, STYLE_STRING, CLR_INVALID, scintillacon.SCE_P_TRIPLE ),
  292. (STYLE_TQDSTRING, STYLE_STRING, CLR_INVALID, scintillacon.SCE_P_TRIPLEDOUBLE),
  293. (STYLE_STRINGEOL, (0, 0, 200, 0, 0x000000), 0x008080, scintillacon.SCE_P_STRINGEOL),
  294. (STYLE_KEYWORD, (0, 1, 200, 0, 0x800000), CLR_INVALID, scintillacon.SCE_P_WORD),
  295. (STYLE_CLASS, (0, 1, 200, 0, 0xFF0000), CLR_INVALID, scintillacon.SCE_P_CLASSNAME ),
  296. (STYLE_METHOD, (0, 1, 200, 0, 0x808000), CLR_INVALID, scintillacon.SCE_P_DEFNAME),
  297. (STYLE_OPERATOR, (0, 0, 200, 0, 0x000000), CLR_INVALID, scintillacon.SCE_P_OPERATOR),
  298. (STYLE_IDENTIFIER, (0, 0, 200, 0, 0x000000), CLR_INVALID, scintillacon.SCE_P_IDENTIFIER ),
  299. ]
  300. # These styles _always_ have this specific style number, regardless of
  301. # internal or external formatter.
  302. SPECIAL_STYLES = [
  303. (STYLE_BRACE, (0, 0, 200, 0, 0x000000), 0xffff80, scintillacon.STYLE_BRACELIGHT),
  304. (STYLE_BRACEBAD, (0, 0, 200, 0, 0x000000), 0x8ea5f2, scintillacon.STYLE_BRACEBAD),
  305. (STYLE_LINENUMBER, (0, 0, 200, 0, 0x000000), win32api.GetSysColor(win32con.COLOR_3DFACE), scintillacon.STYLE_LINENUMBER),
  306. (STYLE_INDENTGUIDE, (0, 0, 200, 0, 0x000000), CLR_INVALID, scintillacon.STYLE_INDENTGUIDE),
  307. ## Not actually a style; requires special handling to send appropriate messages to scintilla
  308. (STYLE_SELECTION, (0, 0, 200, 0, CLR_INVALID), win32api.RGB(0xc0, 0xc0, 0xc0), 999999),
  309. ]
  310. PythonSampleCode = """\
  311. # Some Python
  312. class Sample(Super):
  313. def Fn(self):
  314. \tself.v = 1024
  315. dest = 'dest.html'
  316. x = func(a + 1)|)
  317. s = "I forget...
  318. ## A large
  319. ## comment block"""
  320. class PythonSourceFormatter(Formatter):
  321. string_style_names = STRING_STYLES
  322. def GetSampleText(self):
  323. return PythonSampleCode
  324. def LoadStyles(self):
  325. pass
  326. def SetStyles(self):
  327. for name, format, bg, ignore in PYTHON_STYLES:
  328. self.RegisterStyle( Style(name, format, bg) )
  329. for name, format, bg, sc_id in SPECIAL_STYLES:
  330. self.RegisterStyle( Style(name, format, bg), sc_id )
  331. def ClassifyWord(self, cdoc, start, end, prevWord):
  332. word = cdoc[start:end+1].decode('latin-1')
  333. attr = STYLE_IDENTIFIER
  334. if prevWord == "class":
  335. attr = STYLE_CLASS
  336. elif prevWord == "def":
  337. attr = STYLE_METHOD
  338. elif word[0] in string.digits:
  339. attr = STYLE_NUMBER
  340. elif iskeyword(word):
  341. attr = STYLE_KEYWORD
  342. self.ColorSeg(start, end, attr)
  343. return word
  344. def ColorizeString(self, str, styleStart):
  345. if styleStart is None: styleStart = STYLE_DEFAULT
  346. return self.ColorizePythonCode(str, 0, styleStart)
  347. def ColorizePythonCode(self, cdoc, charStart, styleStart):
  348. # Straight translation of C++, should do better
  349. lengthDoc = len(cdoc)
  350. if lengthDoc <= charStart: return
  351. prevWord = ""
  352. state = styleStart
  353. chPrev = chPrev2 = chPrev3 = ' '
  354. chNext2 = chNext = cdoc[charStart:charStart+1].decode('latin-1')
  355. startSeg = i = charStart
  356. while i < lengthDoc:
  357. ch = chNext
  358. chNext = ' '
  359. if i+1 < lengthDoc: chNext = cdoc[i+1:i+2].decode('latin-1')
  360. chNext2 = ' '
  361. if i+2 < lengthDoc: chNext2 = cdoc[i+2:i+3].decode('latin-1')
  362. if state == STYLE_DEFAULT:
  363. if ch in wordstarts:
  364. self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
  365. state = STYLE_KEYWORD
  366. startSeg = i
  367. elif ch == '#':
  368. self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
  369. if chNext == '#':
  370. state = STYLE_COMMENT_BLOCK
  371. else:
  372. state = STYLE_COMMENT
  373. startSeg = i
  374. elif ch == '\"':
  375. self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
  376. startSeg = i
  377. state = STYLE_COMMENT
  378. if chNext == '\"' and chNext2 == '\"':
  379. i = i + 2
  380. state = STYLE_TQDSTRING
  381. ch = ' '
  382. chPrev = ' '
  383. chNext = ' '
  384. if i+1 < lengthDoc: chNext = cdoc[i+1]
  385. else:
  386. state = STYLE_STRING
  387. elif ch == '\'':
  388. self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
  389. startSeg = i
  390. state = STYLE_COMMENT
  391. if chNext == '\'' and chNext2 == '\'':
  392. i = i + 2
  393. state = STYLE_TQSSTRING
  394. ch = ' '
  395. chPrev = ' '
  396. chNext = ' '
  397. if i+1 < lengthDoc: chNext = cdoc[i+1]
  398. else:
  399. state = STYLE_SQSTRING
  400. elif ch in operators:
  401. self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
  402. self.ColorSeg(i, i, STYLE_OPERATOR)
  403. startSeg = i+1
  404. elif state == STYLE_KEYWORD:
  405. if ch not in wordchars:
  406. prevWord = self.ClassifyWord(cdoc, startSeg, i-1, prevWord)
  407. state = STYLE_DEFAULT
  408. startSeg = i
  409. if ch == '#':
  410. if chNext == '#':
  411. state = STYLE_COMMENT_BLOCK
  412. else:
  413. state = STYLE_COMMENT
  414. elif ch == '\"':
  415. if chNext == '\"' and chNext2 == '\"':
  416. i = i + 2
  417. state = STYLE_TQDSTRING
  418. ch = ' '
  419. chPrev = ' '
  420. chNext = ' '
  421. if i+1 < lengthDoc: chNext = cdoc[i+1]
  422. else:
  423. state = STYLE_STRING
  424. elif ch == '\'':
  425. if chNext == '\'' and chNext2 == '\'':
  426. i = i + 2
  427. state = STYLE_TQSSTRING
  428. ch = ' '
  429. chPrev = ' '
  430. chNext = ' '
  431. if i+1 < lengthDoc: chNext = cdoc[i+1]
  432. else:
  433. state = STYLE_SQSTRING
  434. elif ch in operators:
  435. self.ColorSeg(startSeg, i, STYLE_OPERATOR)
  436. startSeg = i+1
  437. elif state == STYLE_COMMENT or state == STYLE_COMMENT_BLOCK:
  438. if ch == '\r' or ch == '\n':
  439. self.ColorSeg(startSeg, i-1, state)
  440. state = STYLE_DEFAULT
  441. startSeg = i
  442. elif state == STYLE_STRING:
  443. if ch == '\\':
  444. if chNext == '\"' or chNext == '\'' or chNext == '\\':
  445. i = i + 1
  446. ch = chNext
  447. chNext = ' '
  448. if i+1 < lengthDoc: chNext = cdoc[i+1]
  449. elif ch == '\"':
  450. self.ColorSeg(startSeg, i, STYLE_STRING)
  451. state = STYLE_DEFAULT
  452. startSeg = i+1
  453. elif state == STYLE_SQSTRING:
  454. if ch == '\\':
  455. if chNext == '\"' or chNext == '\'' or chNext == '\\':
  456. i = i+1
  457. ch = chNext
  458. chNext = ' '
  459. if i+1 < lengthDoc: chNext = cdoc[i+1]
  460. elif ch == '\'':
  461. self.ColorSeg(startSeg, i, STYLE_SQSTRING)
  462. state = STYLE_DEFAULT
  463. startSeg = i+1
  464. elif state == STYLE_TQSSTRING:
  465. if ch == '\'' and chPrev == '\'' and chPrev2 == '\'' and chPrev3 != '\\':
  466. self.ColorSeg(startSeg, i, STYLE_TQSSTRING)
  467. state = STYLE_DEFAULT
  468. startSeg = i+1
  469. elif state == STYLE_TQDSTRING and ch == '\"' and chPrev == '\"' and chPrev2 == '\"' and chPrev3 != '\\':
  470. self.ColorSeg(startSeg, i, STYLE_TQDSTRING)
  471. state = STYLE_DEFAULT
  472. startSeg = i+1
  473. chPrev3 = chPrev2
  474. chPrev2 = chPrev
  475. chPrev = ch
  476. i = i + 1
  477. if startSeg < lengthDoc:
  478. if state == STYLE_KEYWORD:
  479. self.ClassifyWord(cdoc, startSeg, lengthDoc-1, prevWord)
  480. else:
  481. self.ColorSeg(startSeg, lengthDoc-1, state)
  482. # These taken from the SciTE properties file.
  483. source_formatter_extensions = [
  484. ( ".py .pys .pyw".split(), scintillacon.SCLEX_PYTHON ),
  485. ( ".html .htm .asp .shtml".split(), scintillacon.SCLEX_HTML ),
  486. ( "c .cc .cpp .cxx .h .hh .hpp .hxx .idl .odl .php3 .phtml .inc .js".split(), scintillacon.SCLEX_CPP ),
  487. ( ".vbs .frm .ctl .cls".split(), scintillacon.SCLEX_VB ),
  488. ( ".pl .pm .cgi .pod".split(), scintillacon.SCLEX_PERL ),
  489. ( ".sql .spec .body .sps .spb .sf .sp".split(), scintillacon.SCLEX_SQL ),
  490. ( ".tex .sty".split(), scintillacon.SCLEX_LATEX ),
  491. ( ".xml .xul".split(), scintillacon.SCLEX_XML ),
  492. ( ".err".split(), scintillacon.SCLEX_ERRORLIST ),
  493. ( ".mak".split(), scintillacon.SCLEX_MAKEFILE ),
  494. ( ".bat .cmd".split(), scintillacon.SCLEX_BATCH ),
  495. ]
  496. class BuiltinSourceFormatter(FormatterBase):
  497. # A class that represents a formatter built-in to Scintilla
  498. def __init__(self, scintilla, ext):
  499. self.ext = ext
  500. FormatterBase.__init__(self, scintilla)
  501. def Colorize(self, start=0, end=-1):
  502. self.scintilla.SendScintilla(scintillacon.SCI_COLOURISE, start, end)
  503. def RegisterStyle(self, style, stylenum = None):
  504. assert style.stylenum is None, "Style has already been registered"
  505. if stylenum is None:
  506. stylenum = self.nextstylenum
  507. self.nextstylenum = self.nextstylenum + 1
  508. assert self.styles.get(stylenum) is None, "We are reusing a style number!"
  509. style.stylenum = stylenum
  510. self.styles[style.name] = style
  511. self.styles_by_id[stylenum] = style
  512. def HookFormatter(self, parent = None):
  513. sc = self.scintilla
  514. for exts, formatter in source_formatter_extensions:
  515. if self.ext in exts:
  516. formatter_use = formatter
  517. break
  518. else:
  519. formatter_use = scintillacon.SCLEX_PYTHON
  520. sc.SendScintilla(scintillacon.SCI_SETLEXER, formatter_use)
  521. keywords = ' '.join(kwlist)
  522. sc.SCISetKeywords(keywords)
  523. class BuiltinPythonSourceFormatter(BuiltinSourceFormatter):
  524. sci_lexer_name = scintillacon.SCLEX_PYTHON
  525. string_style_names = STRING_STYLES
  526. def __init__(self, sc, ext = ".py"):
  527. BuiltinSourceFormatter.__init__(self, sc, ext)
  528. def SetStyles(self):
  529. for name, format, bg, sc_id in PYTHON_STYLES:
  530. self.RegisterStyle( Style(name, format, bg), sc_id )
  531. for name, format, bg, sc_id in SPECIAL_STYLES:
  532. self.RegisterStyle( Style(name, format, bg), sc_id )
  533. def GetSampleText(self):
  534. return PythonSampleCode