import stringfrom idlelib.delegator import Delegator# tkinter import not needed because module does not create widgets,# although many methods operate on text widget arguments.#$ event <<redo>>#$ win <Control-y>#$ unix <Alt-z>#$ event <<undo>>#$ win <Control-z>#$ unix <Control-z>#$ event <<dump-undo-state>>#$ win <Control-backslash>#$ unix <Control-backslash>class UndoDelegator(Delegator):max_undo = 1000def __init__(self):Delegator.__init__(self)self.reset_undo()def setdelegate(self, delegate):if self.delegate is not None:self.unbind("<<undo>>")self.unbind("<<redo>>")self.unbind("<<dump-undo-state>>")Delegator.setdelegate(self, delegate)if delegate is not None:self.bind("<<undo>>", self.undo_event)self.bind("<<redo>>", self.redo_event)self.bind("<<dump-undo-state>>", self.dump_event)def dump_event(self, event):from pprint import pprintpprint(self.undolist[:self.pointer])print("pointer:", self.pointer, end=' ')print("saved:", self.saved, end=' ')print("can_merge:", self.can_merge, end=' ')print("get_saved():", self.get_saved())pprint(self.undolist[self.pointer:])return "break"def reset_undo(self):self.was_saved = -1self.pointer = 0self.undolist = []self.undoblock = 0 # or a CommandSequence instanceself.set_saved(1)def set_saved(self, flag):if flag:self.saved = self.pointerelse:self.saved = -1self.can_merge = Falseself.check_saved()def get_saved(self):return self.saved == self.pointersaved_change_hook = Nonedef set_saved_change_hook(self, hook):self.saved_change_hook = hookwas_saved = -1def check_saved(self):is_saved = self.get_saved()if is_saved != self.was_saved:self.was_saved = is_savedif self.saved_change_hook:self.saved_change_hook()def insert(self, index, chars, tags=None):self.addcmd(InsertCommand(index, chars, tags))def delete(self, index1, index2=None):self.addcmd(DeleteCommand(index1, index2))# Clients should call undo_block_start() and undo_block_stop()# around a sequence of editing cmds to be treated as a unit by# undo & redo. Nested matching calls are OK, and the inner calls# then act like nops. OK too if no editing cmds, or only one# editing cmd, is issued in between: if no cmds, the whole# sequence has no effect; and if only one cmd, that cmd is entered# directly into the undo list, as if undo_block_xxx hadn't been# called. The intent of all that is to make this scheme easy# to use: all the client has to worry about is making sure each# _start() call is matched by a _stop() call.def undo_block_start(self):if self.undoblock == 0:self.undoblock = CommandSequence()self.undoblock.bump_depth()def undo_block_stop(self):if self.undoblock.bump_depth(-1) == 0:cmd = self.undoblockself.undoblock = 0if len(cmd) > 0:if len(cmd) == 1:# no need to wrap a single cmdcmd = cmd.getcmd(0)# this blk of cmds, or single cmd, has already# been done, so don't execute it againself.addcmd(cmd, 0)def addcmd(self, cmd, execute=True):if execute:cmd.do(self.delegate)if self.undoblock != 0:self.undoblock.append(cmd)returnif self.can_merge and self.pointer > 0:lastcmd = self.undolist[self.pointer-1]if lastcmd.merge(cmd):returnself.undolist[self.pointer:] = [cmd]if self.saved > self.pointer:self.saved = -1self.pointer = self.pointer + 1if len(self.undolist) > self.max_undo:##print "truncating undo list"del self.undolist[0]self.pointer = self.pointer - 1if self.saved >= 0:self.saved = self.saved - 1self.can_merge = Trueself.check_saved()def undo_event(self, event):if self.pointer == 0:self.bell()return "break"cmd = self.undolist[self.pointer - 1]cmd.undo(self.delegate)self.pointer = self.pointer - 1self.can_merge = Falseself.check_saved()return "break"def redo_event(self, event):if self.pointer >= len(self.undolist):self.bell()return "break"cmd = self.undolist[self.pointer]cmd.redo(self.delegate)self.pointer = self.pointer + 1self.can_merge = Falseself.check_saved()return "break"class Command:# Base class for Undoable commandstags = Nonedef __init__(self, index1, index2, chars, tags=None):self.marks_before = {}self.marks_after = {}self.index1 = index1self.index2 = index2self.chars = charsif tags:self.tags = tagsdef __repr__(self):s = self.__class__.__name__t = (self.index1, self.index2, self.chars, self.tags)if self.tags is None:t = t[:-1]return s + repr(t)def do(self, text):passdef redo(self, text):passdef undo(self, text):passdef merge(self, cmd):return 0def save_marks(self, text):marks = {}for name in text.mark_names():if name != "insert" and name != "current":marks[name] = text.index(name)return marksdef set_marks(self, text, marks):for name, index in marks.items():text.mark_set(name, index)class InsertCommand(Command):# Undoable insert commanddef __init__(self, index1, chars, tags=None):Command.__init__(self, index1, None, chars, tags)def do(self, text):self.marks_before = self.save_marks(text)self.index1 = text.index(self.index1)if text.compare(self.index1, ">", "end-1c"):# Insert before the final newlineself.index1 = text.index("end-1c")text.insert(self.index1, self.chars, self.tags)self.index2 = text.index("%s+%dc" % (self.index1, len(self.chars)))self.marks_after = self.save_marks(text)##sys.__stderr__.write("do: %s\n" % self)def redo(self, text):text.mark_set('insert', self.index1)text.insert(self.index1, self.chars, self.tags)self.set_marks(text, self.marks_after)text.see('insert')##sys.__stderr__.write("redo: %s\n" % self)def undo(self, text):text.mark_set('insert', self.index1)text.delete(self.index1, self.index2)self.set_marks(text, self.marks_before)text.see('insert')##sys.__stderr__.write("undo: %s\n" % self)def merge(self, cmd):if self.__class__ is not cmd.__class__:return Falseif self.index2 != cmd.index1:return Falseif self.tags != cmd.tags:return Falseif len(cmd.chars) != 1:return Falseif self.chars and \self.classify(self.chars[-1]) != self.classify(cmd.chars):return Falseself.index2 = cmd.index2self.chars = self.chars + cmd.charsreturn Truealphanumeric = string.ascii_letters + string.digits + "_"def classify(self, c):if c in self.alphanumeric:return "alphanumeric"if c == "\n":return "newline"return "punctuation"class DeleteCommand(Command):# Undoable delete commanddef __init__(self, index1, index2=None):Command.__init__(self, index1, index2, None, None)def do(self, text):self.marks_before = self.save_marks(text)self.index1 = text.index(self.index1)if self.index2:self.index2 = text.index(self.index2)else:self.index2 = text.index(self.index1 + " +1c")if text.compare(self.index2, ">", "end-1c"):# Don't delete the final newlineself.index2 = text.index("end-1c")self.chars = text.get(self.index1, self.index2)text.delete(self.index1, self.index2)self.marks_after = self.save_marks(text)##sys.__stderr__.write("do: %s\n" % self)def redo(self, text):text.mark_set('insert', self.index1)text.delete(self.index1, self.index2)self.set_marks(text, self.marks_after)text.see('insert')##sys.__stderr__.write("redo: %s\n" % self)def undo(self, text):text.mark_set('insert', self.index1)text.insert(self.index1, self.chars)self.set_marks(text, self.marks_before)text.see('insert')##sys.__stderr__.write("undo: %s\n" % self)class CommandSequence(Command):# Wrapper for a sequence of undoable cmds to be undone/redone# as a unitdef __init__(self):self.cmds = []self.depth = 0def __repr__(self):s = self.__class__.__name__strs = []for cmd in self.cmds:strs.append(" %r" % (cmd,))return s + "(\n" + ",\n".join(strs) + "\n)"def __len__(self):return len(self.cmds)def append(self, cmd):self.cmds.append(cmd)def getcmd(self, i):return self.cmds[i]def redo(self, text):for cmd in self.cmds:cmd.redo(text)def undo(self, text):cmds = self.cmds[:]cmds.reverse()for cmd in cmds:cmd.undo(text)def bump_depth(self, incr=1):self.depth = self.depth + incrreturn self.depthdef _undo_delegator(parent): # htest #from tkinter import Toplevel, Text, Buttonfrom idlelib.percolator import Percolatorundowin = Toplevel(parent)undowin.title("Test UndoDelegator")x, y = map(int, parent.geometry().split('+')[1:])undowin.geometry("+%d+%d" % (x, y + 175))text = Text(undowin, height=10)text.pack()text.focus_set()p = Percolator(text)d = UndoDelegator()p.insertfilter(d)undo = Button(undowin, text="Undo", command=lambda:d.undo_event(None))undo.pack(side='left')redo = Button(undowin, text="Redo", command=lambda:d.redo_event(None))redo.pack(side='left')dump = Button(undowin, text="Dump", command=lambda:d.dump_event(None))dump.pack(side='left')if __name__ == "__main__":from unittest import mainmain('idlelib.idle_test.test_undo', verbosity=2, exit=False)from idlelib.idle_test.htest import runrun(_undo_delegator)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。