PageRenderTime 41ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/Windows/Python3.8/WPy64-3830/WPy64-3830/python-3.8.3.amd64/Lib/idlelib/colorizer.py

https://gitlab.com/abhi1tb/build
Python | 336 lines | 262 code | 26 blank | 48 comment | 44 complexity | 22e61431df8eee13e1280c40babd8bcb MD5 | raw file
  1. import builtins
  2. import keyword
  3. import re
  4. import time
  5. from idlelib.config import idleConf
  6. from idlelib.delegator import Delegator
  7. DEBUG = False
  8. def any(name, alternates):
  9. "Return a named group pattern matching list of alternates."
  10. return "(?P<%s>" % name + "|".join(alternates) + ")"
  11. def make_pat():
  12. kw = r"\b" + any("KEYWORD", keyword.kwlist) + r"\b"
  13. builtinlist = [str(name) for name in dir(builtins)
  14. if not name.startswith('_') and \
  15. name not in keyword.kwlist]
  16. builtin = r"([^.'\"\\#]\b|^)" + any("BUILTIN", builtinlist) + r"\b"
  17. comment = any("COMMENT", [r"#[^\n]*"])
  18. stringprefix = r"(?i:r|u|f|fr|rf|b|br|rb)?"
  19. sqstring = stringprefix + r"'[^'\\\n]*(\\.[^'\\\n]*)*'?"
  20. dqstring = stringprefix + r'"[^"\\\n]*(\\.[^"\\\n]*)*"?'
  21. sq3string = stringprefix + r"'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?"
  22. dq3string = stringprefix + r'"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?'
  23. string = any("STRING", [sq3string, dq3string, sqstring, dqstring])
  24. return kw + "|" + builtin + "|" + comment + "|" + string +\
  25. "|" + any("SYNC", [r"\n"])
  26. prog = re.compile(make_pat(), re.S)
  27. idprog = re.compile(r"\s+(\w+)", re.S)
  28. def color_config(text):
  29. """Set color options of Text widget.
  30. If ColorDelegator is used, this should be called first.
  31. """
  32. # Called from htest, TextFrame, Editor, and Turtledemo.
  33. # Not automatic because ColorDelegator does not know 'text'.
  34. theme = idleConf.CurrentTheme()
  35. normal_colors = idleConf.GetHighlight(theme, 'normal')
  36. cursor_color = idleConf.GetHighlight(theme, 'cursor')['foreground']
  37. select_colors = idleConf.GetHighlight(theme, 'hilite')
  38. text.config(
  39. foreground=normal_colors['foreground'],
  40. background=normal_colors['background'],
  41. insertbackground=cursor_color,
  42. selectforeground=select_colors['foreground'],
  43. selectbackground=select_colors['background'],
  44. inactiveselectbackground=select_colors['background'], # new in 8.5
  45. )
  46. class ColorDelegator(Delegator):
  47. """Delegator for syntax highlighting (text coloring).
  48. Instance variables:
  49. delegate: Delegator below this one in the stack, meaning the
  50. one this one delegates to.
  51. Used to track state:
  52. after_id: Identifier for scheduled after event, which is a
  53. timer for colorizing the text.
  54. allow_colorizing: Boolean toggle for applying colorizing.
  55. colorizing: Boolean flag when colorizing is in process.
  56. stop_colorizing: Boolean flag to end an active colorizing
  57. process.
  58. """
  59. def __init__(self):
  60. Delegator.__init__(self)
  61. self.init_state()
  62. self.prog = prog
  63. self.idprog = idprog
  64. self.LoadTagDefs()
  65. def init_state(self):
  66. "Initialize variables that track colorizing state."
  67. self.after_id = None
  68. self.allow_colorizing = True
  69. self.stop_colorizing = False
  70. self.colorizing = False
  71. def setdelegate(self, delegate):
  72. """Set the delegate for this instance.
  73. A delegate is an instance of a Delegator class and each
  74. delegate points to the next delegator in the stack. This
  75. allows multiple delegators to be chained together for a
  76. widget. The bottom delegate for a colorizer is a Text
  77. widget.
  78. If there is a delegate, also start the colorizing process.
  79. """
  80. if self.delegate is not None:
  81. self.unbind("<<toggle-auto-coloring>>")
  82. Delegator.setdelegate(self, delegate)
  83. if delegate is not None:
  84. self.config_colors()
  85. self.bind("<<toggle-auto-coloring>>", self.toggle_colorize_event)
  86. self.notify_range("1.0", "end")
  87. else:
  88. # No delegate - stop any colorizing.
  89. self.stop_colorizing = True
  90. self.allow_colorizing = False
  91. def config_colors(self):
  92. "Configure text widget tags with colors from tagdefs."
  93. for tag, cnf in self.tagdefs.items():
  94. self.tag_configure(tag, **cnf)
  95. self.tag_raise('sel')
  96. def LoadTagDefs(self):
  97. "Create dictionary of tag names to text colors."
  98. theme = idleConf.CurrentTheme()
  99. self.tagdefs = {
  100. "COMMENT": idleConf.GetHighlight(theme, "comment"),
  101. "KEYWORD": idleConf.GetHighlight(theme, "keyword"),
  102. "BUILTIN": idleConf.GetHighlight(theme, "builtin"),
  103. "STRING": idleConf.GetHighlight(theme, "string"),
  104. "DEFINITION": idleConf.GetHighlight(theme, "definition"),
  105. "SYNC": {'background':None,'foreground':None},
  106. "TODO": {'background':None,'foreground':None},
  107. "ERROR": idleConf.GetHighlight(theme, "error"),
  108. # The following is used by ReplaceDialog:
  109. "hit": idleConf.GetHighlight(theme, "hit"),
  110. }
  111. if DEBUG: print('tagdefs',self.tagdefs)
  112. def insert(self, index, chars, tags=None):
  113. "Insert chars into widget at index and mark for colorizing."
  114. index = self.index(index)
  115. self.delegate.insert(index, chars, tags)
  116. self.notify_range(index, index + "+%dc" % len(chars))
  117. def delete(self, index1, index2=None):
  118. "Delete chars between indexes and mark for colorizing."
  119. index1 = self.index(index1)
  120. self.delegate.delete(index1, index2)
  121. self.notify_range(index1)
  122. def notify_range(self, index1, index2=None):
  123. "Mark text changes for processing and restart colorizing, if active."
  124. self.tag_add("TODO", index1, index2)
  125. if self.after_id:
  126. if DEBUG: print("colorizing already scheduled")
  127. return
  128. if self.colorizing:
  129. self.stop_colorizing = True
  130. if DEBUG: print("stop colorizing")
  131. if self.allow_colorizing:
  132. if DEBUG: print("schedule colorizing")
  133. self.after_id = self.after(1, self.recolorize)
  134. return
  135. def close(self):
  136. if self.after_id:
  137. after_id = self.after_id
  138. self.after_id = None
  139. if DEBUG: print("cancel scheduled recolorizer")
  140. self.after_cancel(after_id)
  141. self.allow_colorizing = False
  142. self.stop_colorizing = True
  143. def toggle_colorize_event(self, event=None):
  144. """Toggle colorizing on and off.
  145. When toggling off, if colorizing is scheduled or is in
  146. process, it will be cancelled and/or stopped.
  147. When toggling on, colorizing will be scheduled.
  148. """
  149. if self.after_id:
  150. after_id = self.after_id
  151. self.after_id = None
  152. if DEBUG: print("cancel scheduled recolorizer")
  153. self.after_cancel(after_id)
  154. if self.allow_colorizing and self.colorizing:
  155. if DEBUG: print("stop colorizing")
  156. self.stop_colorizing = True
  157. self.allow_colorizing = not self.allow_colorizing
  158. if self.allow_colorizing and not self.colorizing:
  159. self.after_id = self.after(1, self.recolorize)
  160. if DEBUG:
  161. print("auto colorizing turned",\
  162. self.allow_colorizing and "on" or "off")
  163. return "break"
  164. def recolorize(self):
  165. """Timer event (every 1ms) to colorize text.
  166. Colorizing is only attempted when the text widget exists,
  167. when colorizing is toggled on, and when the colorizing
  168. process is not already running.
  169. After colorizing is complete, some cleanup is done to
  170. make sure that all the text has been colorized.
  171. """
  172. self.after_id = None
  173. if not self.delegate:
  174. if DEBUG: print("no delegate")
  175. return
  176. if not self.allow_colorizing:
  177. if DEBUG: print("auto colorizing is off")
  178. return
  179. if self.colorizing:
  180. if DEBUG: print("already colorizing")
  181. return
  182. try:
  183. self.stop_colorizing = False
  184. self.colorizing = True
  185. if DEBUG: print("colorizing...")
  186. t0 = time.perf_counter()
  187. self.recolorize_main()
  188. t1 = time.perf_counter()
  189. if DEBUG: print("%.3f seconds" % (t1-t0))
  190. finally:
  191. self.colorizing = False
  192. if self.allow_colorizing and self.tag_nextrange("TODO", "1.0"):
  193. if DEBUG: print("reschedule colorizing")
  194. self.after_id = self.after(1, self.recolorize)
  195. def recolorize_main(self):
  196. "Evaluate text and apply colorizing tags."
  197. next = "1.0"
  198. while True:
  199. item = self.tag_nextrange("TODO", next)
  200. if not item:
  201. break
  202. head, tail = item
  203. self.tag_remove("SYNC", head, tail)
  204. item = self.tag_prevrange("SYNC", head)
  205. if item:
  206. head = item[1]
  207. else:
  208. head = "1.0"
  209. chars = ""
  210. next = head
  211. lines_to_get = 1
  212. ok = False
  213. while not ok:
  214. mark = next
  215. next = self.index(mark + "+%d lines linestart" %
  216. lines_to_get)
  217. lines_to_get = min(lines_to_get * 2, 100)
  218. ok = "SYNC" in self.tag_names(next + "-1c")
  219. line = self.get(mark, next)
  220. ##print head, "get", mark, next, "->", repr(line)
  221. if not line:
  222. return
  223. for tag in self.tagdefs:
  224. self.tag_remove(tag, mark, next)
  225. chars = chars + line
  226. m = self.prog.search(chars)
  227. while m:
  228. for key, value in m.groupdict().items():
  229. if value:
  230. a, b = m.span(key)
  231. self.tag_add(key,
  232. head + "+%dc" % a,
  233. head + "+%dc" % b)
  234. if value in ("def", "class"):
  235. m1 = self.idprog.match(chars, b)
  236. if m1:
  237. a, b = m1.span(1)
  238. self.tag_add("DEFINITION",
  239. head + "+%dc" % a,
  240. head + "+%dc" % b)
  241. m = self.prog.search(chars, m.end())
  242. if "SYNC" in self.tag_names(next + "-1c"):
  243. head = next
  244. chars = ""
  245. else:
  246. ok = False
  247. if not ok:
  248. # We're in an inconsistent state, and the call to
  249. # update may tell us to stop. It may also change
  250. # the correct value for "next" (since this is a
  251. # line.col string, not a true mark). So leave a
  252. # crumb telling the next invocation to resume here
  253. # in case update tells us to leave.
  254. self.tag_add("TODO", next)
  255. self.update()
  256. if self.stop_colorizing:
  257. if DEBUG: print("colorizing stopped")
  258. return
  259. def removecolors(self):
  260. "Remove all colorizing tags."
  261. for tag in self.tagdefs:
  262. self.tag_remove(tag, "1.0", "end")
  263. def _color_delegator(parent): # htest #
  264. from tkinter import Toplevel, Text
  265. from idlelib.percolator import Percolator
  266. top = Toplevel(parent)
  267. top.title("Test ColorDelegator")
  268. x, y = map(int, parent.geometry().split('+')[1:])
  269. top.geometry("700x250+%d+%d" % (x + 20, y + 175))
  270. source = (
  271. "if True: int ('1') # keyword, builtin, string, comment\n"
  272. "elif False: print(0)\n"
  273. "else: float(None)\n"
  274. "if iF + If + IF: 'keyword matching must respect case'\n"
  275. "if'': x or'' # valid string-keyword no-space combinations\n"
  276. "async def f(): await g()\n"
  277. "# All valid prefixes for unicode and byte strings should be colored.\n"
  278. "'x', '''x''', \"x\", \"\"\"x\"\"\"\n"
  279. "r'x', u'x', R'x', U'x', f'x', F'x'\n"
  280. "fr'x', Fr'x', fR'x', FR'x', rf'x', rF'x', Rf'x', RF'x'\n"
  281. "b'x',B'x', br'x',Br'x',bR'x',BR'x', rb'x', rB'x',Rb'x',RB'x'\n"
  282. "# Invalid combinations of legal characters should be half colored.\n"
  283. "ur'x', ru'x', uf'x', fu'x', UR'x', ufr'x', rfu'x', xf'x', fx'x'\n"
  284. )
  285. text = Text(top, background="white")
  286. text.pack(expand=1, fill="both")
  287. text.insert("insert", source)
  288. text.focus_set()
  289. color_config(text)
  290. p = Percolator(text)
  291. d = ColorDelegator()
  292. p.insertfilter(d)
  293. if __name__ == "__main__":
  294. from unittest import main
  295. main('idlelib.idle_test.test_colorizer', verbosity=2, exit=False)
  296. from idlelib.idle_test.htest import run
  297. run(_color_delegator)