mirror of
https://github.com/python/cpython
synced 2024-11-05 18:12:54 +00:00
26c2cb4bd1
Ho ho ho -- that's trickier than it sounded! The colorizer is working with "line.col" strings instead of Text marks, and the absolute coordinates of the point of interest can change across the self.update call (voice of baffled experience, when two quick backspaces no longer fooled it, but a backspace followed by a quick ENTER did <wink>). Anyway, the attached appears to do the trick. CPU usage goes way up when typing quickly into a long triple-quoted string, but the latency is fine for me (a relatively fast typist on a relatively slow machine). Most of the changes here are left over from reducing the # of vrbl names to help me reason about the logic better; I hope the code is a *little* easier to
245 lines
8.6 KiB
Python
245 lines
8.6 KiB
Python
import time
|
|
import string
|
|
import re
|
|
import keyword
|
|
from Tkinter import *
|
|
from Delegator import Delegator
|
|
import IdlePrefs
|
|
|
|
#$ event <<toggle-auto-coloring>>
|
|
#$ win <Control-slash>
|
|
#$ unix <Control-slash>
|
|
|
|
__debug__ = 0
|
|
|
|
|
|
def any(name, list):
|
|
return "(?P<%s>" % name + string.join(list, "|") + ")"
|
|
|
|
def make_pat():
|
|
kw = r"\b" + any("KEYWORD", keyword.kwlist) + r"\b"
|
|
comment = any("COMMENT", [r"#[^\n]*"])
|
|
sqstring = r"(\b[rR])?'[^'\\\n]*(\\.[^'\\\n]*)*'?"
|
|
dqstring = r'(\b[rR])?"[^"\\\n]*(\\.[^"\\\n]*)*"?'
|
|
sq3string = r"(\b[rR])?'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?"
|
|
dq3string = r'(\b[rR])?"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?'
|
|
string = any("STRING", [sq3string, dq3string, sqstring, dqstring])
|
|
return kw + "|" + 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
|
|
|
|
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")
|
|
|
|
def config_colors(self):
|
|
for tag, cnf in self.tagdefs.items():
|
|
if cnf:
|
|
apply(self.tag_configure, (tag,), cnf)
|
|
self.tag_raise('sel')
|
|
|
|
cprefs = IdlePrefs.ColorPrefs()
|
|
|
|
tagdefs = {
|
|
"COMMENT": {"foreground": cprefs.CComment[0],
|
|
"background": cprefs.CComment[1]},
|
|
"KEYWORD": {"foreground": cprefs.CKeyword[0],
|
|
"background": cprefs.CKeyword[1]},
|
|
"STRING": {"foreground": cprefs.CString[0],
|
|
"background": cprefs.CString[1]},
|
|
"DEFINITION": {"foreground": cprefs.CDefinition[0],
|
|
"background": cprefs.CDefinition[1]},
|
|
|
|
"SYNC": {"background": cprefs.CSync[0],
|
|
"background": cprefs.CSync[1]},
|
|
"TODO": {"background": cprefs.CTodo[0],
|
|
"background": cprefs.CTodo[1]},
|
|
|
|
"BREAK": {"background": cprefs.CBreak[0],
|
|
"background": cprefs.CBreak[1]},
|
|
|
|
# The following is used by ReplaceDialog:
|
|
"hit": {"foreground": cprefs.CHit[0],
|
|
"background": cprefs.CHit[1]},
|
|
}
|
|
|
|
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 = 1
|
|
colorizing = 0
|
|
|
|
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 = 1
|
|
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 = 0
|
|
self.stop_colorizing = 1
|
|
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 = 1
|
|
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 = 0
|
|
self.colorizing = 1
|
|
if __debug__: print "colorizing..."
|
|
t0 = time.clock()
|
|
self.recolorize_main()
|
|
t1 = time.clock()
|
|
if __debug__: print "%.3f seconds" % (t1-t0)
|
|
finally:
|
|
self.colorizing = 0
|
|
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 1:
|
|
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 = 0
|
|
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, "->", `line`
|
|
if not line:
|
|
return
|
|
for tag in self.tagdefs.keys():
|
|
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 = 0
|
|
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 main():
|
|
from Percolator import Percolator
|
|
root = Tk()
|
|
root.wm_protocol("WM_DELETE_WINDOW", root.quit)
|
|
text = Text(background="white")
|
|
text.pack(expand=1, fill="both")
|
|
text.focus_set()
|
|
p = Percolator(text)
|
|
d = ColorDelegator()
|
|
p.insertfilter(d)
|
|
root.mainloop()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|