address the compatibility problem introduced by the addition of IDLE Dark. Consistently use the revised idleConf.CurrentTheme everywhere in idlelib.
257 lines
9.5 KiB
Python
257 lines
9.5 KiB
Python
import time
|
|
import re
|
|
import keyword
|
|
import builtins
|
|
from idlelib.Delegator import Delegator
|
|
from idlelib.configHandler import idleConf
|
|
|
|
DEBUG = False
|
|
|
|
def any(name, alternates):
|
|
"Return a named group pattern matching list of alternates."
|
|
return "(?P<%s>" % name + "|".join(alternates) + ")"
|
|
|
|
def make_pat():
|
|
kw = r"\b" + any("KEYWORD", keyword.kwlist) + r"\b"
|
|
builtinlist = [str(name) for name in dir(builtins)
|
|
if not name.startswith('_') and \
|
|
name not in keyword.kwlist]
|
|
# self.file = open("file") :
|
|
# 1st 'file' colorized normal, 2nd as builtin, 3rd as string
|
|
builtin = r"([^.'\"\\#]\b|^)" + any("BUILTIN", builtinlist) + r"\b"
|
|
comment = any("COMMENT", [r"#[^\n]*"])
|
|
stringprefix = r"(\br|u|ur|R|U|UR|Ur|uR|b|B|br|Br|bR|BR|rb|rB|Rb|RB)?"
|
|
sqstring = stringprefix + r"'[^'\\\n]*(\\.[^'\\\n]*)*'?"
|
|
dqstring = stringprefix + r'"[^"\\\n]*(\\.[^"\\\n]*)*"?'
|
|
sq3string = stringprefix + r"'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?"
|
|
dq3string = stringprefix + r'"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?'
|
|
string = any("STRING", [sq3string, dq3string, sqstring, dqstring])
|
|
return kw + "|" + builtin + "|" + comment + "|" + string +\
|
|
"|" + any("SYNC", [r"\n"])
|
|
|
|
prog = re.compile(make_pat(), re.S)
|
|
idprog = re.compile(r"\s+(\w+)", re.S)
|
|
|
|
class ColorDelegator(Delegator):
|
|
|
|
def __init__(self):
|
|
Delegator.__init__(self)
|
|
self.prog = prog
|
|
self.idprog = idprog
|
|
self.LoadTagDefs()
|
|
|
|
def setdelegate(self, delegate):
|
|
if self.delegate is not None:
|
|
self.unbind("<<toggle-auto-coloring>>")
|
|
Delegator.setdelegate(self, delegate)
|
|
if delegate is not None:
|
|
self.config_colors()
|
|
self.bind("<<toggle-auto-coloring>>", self.toggle_colorize_event)
|
|
self.notify_range("1.0", "end")
|
|
else:
|
|
# No delegate - stop any colorizing
|
|
self.stop_colorizing = True
|
|
self.allow_colorizing = False
|
|
|
|
def config_colors(self):
|
|
for tag, cnf in self.tagdefs.items():
|
|
if cnf:
|
|
self.tag_configure(tag, **cnf)
|
|
self.tag_raise('sel')
|
|
|
|
def LoadTagDefs(self):
|
|
theme = idleConf.CurrentTheme()
|
|
self.tagdefs = {
|
|
"COMMENT": idleConf.GetHighlight(theme, "comment"),
|
|
"KEYWORD": idleConf.GetHighlight(theme, "keyword"),
|
|
"BUILTIN": idleConf.GetHighlight(theme, "builtin"),
|
|
"STRING": idleConf.GetHighlight(theme, "string"),
|
|
"DEFINITION": idleConf.GetHighlight(theme, "definition"),
|
|
"SYNC": {'background':None,'foreground':None},
|
|
"TODO": {'background':None,'foreground':None},
|
|
"ERROR": idleConf.GetHighlight(theme, "error"),
|
|
# The following is used by ReplaceDialog:
|
|
"hit": idleConf.GetHighlight(theme, "hit"),
|
|
}
|
|
|
|
if DEBUG: print('tagdefs',self.tagdefs)
|
|
|
|
def insert(self, index, chars, tags=None):
|
|
index = self.index(index)
|
|
self.delegate.insert(index, chars, tags)
|
|
self.notify_range(index, index + "+%dc" % len(chars))
|
|
|
|
def delete(self, index1, index2=None):
|
|
index1 = self.index(index1)
|
|
self.delegate.delete(index1, index2)
|
|
self.notify_range(index1)
|
|
|
|
after_id = None
|
|
allow_colorizing = True
|
|
colorizing = False
|
|
|
|
def notify_range(self, index1, index2=None):
|
|
self.tag_add("TODO", index1, index2)
|
|
if self.after_id:
|
|
if DEBUG: print("colorizing already scheduled")
|
|
return
|
|
if self.colorizing:
|
|
self.stop_colorizing = True
|
|
if DEBUG: print("stop colorizing")
|
|
if self.allow_colorizing:
|
|
if DEBUG: print("schedule colorizing")
|
|
self.after_id = self.after(1, self.recolorize)
|
|
|
|
close_when_done = None # Window to be closed when done colorizing
|
|
|
|
def close(self, close_when_done=None):
|
|
if self.after_id:
|
|
after_id = self.after_id
|
|
self.after_id = None
|
|
if DEBUG: print("cancel scheduled recolorizer")
|
|
self.after_cancel(after_id)
|
|
self.allow_colorizing = False
|
|
self.stop_colorizing = True
|
|
if close_when_done:
|
|
if not self.colorizing:
|
|
close_when_done.destroy()
|
|
else:
|
|
self.close_when_done = close_when_done
|
|
|
|
def toggle_colorize_event(self, event):
|
|
if self.after_id:
|
|
after_id = self.after_id
|
|
self.after_id = None
|
|
if DEBUG: print("cancel scheduled recolorizer")
|
|
self.after_cancel(after_id)
|
|
if self.allow_colorizing and self.colorizing:
|
|
if DEBUG: print("stop colorizing")
|
|
self.stop_colorizing = True
|
|
self.allow_colorizing = not self.allow_colorizing
|
|
if self.allow_colorizing and not self.colorizing:
|
|
self.after_id = self.after(1, self.recolorize)
|
|
if DEBUG:
|
|
print("auto colorizing turned",\
|
|
self.allow_colorizing and "on" or "off")
|
|
return "break"
|
|
|
|
def recolorize(self):
|
|
self.after_id = None
|
|
if not self.delegate:
|
|
if DEBUG: print("no delegate")
|
|
return
|
|
if not self.allow_colorizing:
|
|
if DEBUG: print("auto colorizing is off")
|
|
return
|
|
if self.colorizing:
|
|
if DEBUG: print("already colorizing")
|
|
return
|
|
try:
|
|
self.stop_colorizing = False
|
|
self.colorizing = True
|
|
if DEBUG: print("colorizing...")
|
|
t0 = time.perf_counter()
|
|
self.recolorize_main()
|
|
t1 = time.perf_counter()
|
|
if DEBUG: print("%.3f seconds" % (t1-t0))
|
|
finally:
|
|
self.colorizing = False
|
|
if self.allow_colorizing and self.tag_nextrange("TODO", "1.0"):
|
|
if DEBUG: print("reschedule colorizing")
|
|
self.after_id = self.after(1, self.recolorize)
|
|
if self.close_when_done:
|
|
top = self.close_when_done
|
|
self.close_when_done = None
|
|
top.destroy()
|
|
|
|
def recolorize_main(self):
|
|
next = "1.0"
|
|
while True:
|
|
item = self.tag_nextrange("TODO", next)
|
|
if not item:
|
|
break
|
|
head, tail = item
|
|
self.tag_remove("SYNC", head, tail)
|
|
item = self.tag_prevrange("SYNC", head)
|
|
if item:
|
|
head = item[1]
|
|
else:
|
|
head = "1.0"
|
|
|
|
chars = ""
|
|
next = head
|
|
lines_to_get = 1
|
|
ok = False
|
|
while not ok:
|
|
mark = next
|
|
next = self.index(mark + "+%d lines linestart" %
|
|
lines_to_get)
|
|
lines_to_get = min(lines_to_get * 2, 100)
|
|
ok = "SYNC" in self.tag_names(next + "-1c")
|
|
line = self.get(mark, next)
|
|
##print head, "get", mark, next, "->", repr(line)
|
|
if not line:
|
|
return
|
|
for tag in self.tagdefs:
|
|
self.tag_remove(tag, mark, next)
|
|
chars = chars + line
|
|
m = self.prog.search(chars)
|
|
while m:
|
|
for key, value in m.groupdict().items():
|
|
if value:
|
|
a, b = m.span(key)
|
|
self.tag_add(key,
|
|
head + "+%dc" % a,
|
|
head + "+%dc" % b)
|
|
if value in ("def", "class"):
|
|
m1 = self.idprog.match(chars, b)
|
|
if m1:
|
|
a, b = m1.span(1)
|
|
self.tag_add("DEFINITION",
|
|
head + "+%dc" % a,
|
|
head + "+%dc" % b)
|
|
m = self.prog.search(chars, m.end())
|
|
if "SYNC" in self.tag_names(next + "-1c"):
|
|
head = next
|
|
chars = ""
|
|
else:
|
|
ok = False
|
|
if not ok:
|
|
# We're in an inconsistent state, and the call to
|
|
# update may tell us to stop. It may also change
|
|
# the correct value for "next" (since this is a
|
|
# line.col string, not a true mark). So leave a
|
|
# crumb telling the next invocation to resume here
|
|
# in case update tells us to leave.
|
|
self.tag_add("TODO", next)
|
|
self.update()
|
|
if self.stop_colorizing:
|
|
if DEBUG: print("colorizing stopped")
|
|
return
|
|
|
|
def removecolors(self):
|
|
for tag in self.tagdefs:
|
|
self.tag_remove(tag, "1.0", "end")
|
|
|
|
def _color_delegator(parent): # htest #
|
|
from tkinter import Toplevel, Text
|
|
from idlelib.Percolator import Percolator
|
|
|
|
top = Toplevel(parent)
|
|
top.title("Test ColorDelegator")
|
|
top.geometry("200x100+%d+%d" % (parent.winfo_rootx() + 200,
|
|
parent.winfo_rooty() + 150))
|
|
source = "if somename: x = 'abc' # comment\nprint\n"
|
|
text = Text(top, background="white")
|
|
text.pack(expand=1, fill="both")
|
|
text.insert("insert", source)
|
|
text.focus_set()
|
|
|
|
p = Percolator(text)
|
|
d = ColorDelegator()
|
|
p.insertfilter(d)
|
|
|
|
if __name__ == "__main__":
|
|
from idlelib.idle_test.htest import run
|
|
run(_color_delegator)
|