From 318a70d976fbf225de3485bbbc61b7fe1f24f4a2 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 3 May 1999 15:49:52 +0000 Subject: [PATCH] Tim Peters writes: I'm still unsure, but couldn't stand the virtual event trickery so tried a different sin (adding undo_block_start/stop methods to the Text instance in EditorWindow.py). Like it or not, it's efficient and works . Better idea? Give the attached a whirl. Even if you hate the implementation, I think you'll like the results. Think I caught all the "block edit" cmds, including Format Paragraph, plus subtler ones involving smart indents and backspacing. --- Tools/idle/AutoIndent.py | 82 ++++++++++++++++++++--------------- Tools/idle/EditorWindow.py | 2 + Tools/idle/FormatParagraph.py | 2 + Tools/idle/UndoDelegator.py | 75 +++++++++++++++++++++++++++++++- 4 files changed, 123 insertions(+), 38 deletions(-) diff --git a/Tools/idle/AutoIndent.py b/Tools/idle/AutoIndent.py index 4de7ad09d8f..386490d5138 100644 --- a/Tools/idle/AutoIndent.py +++ b/Tools/idle/AutoIndent.py @@ -142,20 +142,24 @@ class AutoIndent: last = text.index("sel.last") except TclError: first = last = None - if first and last: - if index2line(first) != index2line(last): - return self.indent_region_event(event) - text.delete(first, last) - text.mark_set("insert", first) - if self.prefertabs: - pad = '\t' - else: - n = len(self.spaceindent) - prefix = text.get("insert linestart", "insert") - pad = ' ' * (n - len(prefix) % n) - text.insert("insert", pad) - text.see("insert") - return "break" + text.undo_block_start() + try: + if first and last: + if index2line(first) != index2line(last): + return self.indent_region_event(event) + text.delete(first, last) + text.mark_set("insert", first) + if self.prefertabs: + pad = '\t' + else: + n = len(self.spaceindent) + prefix = text.get("insert linestart", "insert") + pad = ' ' * (n - len(prefix) % n) + text.insert("insert", pad) + text.see("insert") + return "break" + finally: + text.undo_block_stop() def newline_and_indent_event(self, event): text = self.text @@ -164,28 +168,32 @@ class AutoIndent: last = text.index("sel.last") except TclError: first = last = None - if first and last: - text.delete(first, last) - text.mark_set("insert", first) - line = text.get("insert linestart", "insert") - i, n = 0, len(line) - while i < n and line[i] in " \t": - i = i+1 - indent = line[:i] - # strip trailing whitespace - i = 0 - while line and line[-1] in " \t": - line = line[:-1] - i = i + 1 - if i: - text.delete("insert - %d chars" % i, "insert") - text.insert("insert", "\n" + indent) - if _is_block_opener(line): - self.smart_indent_event(event) - elif indent and _is_block_closer(line) and line[-1:] != "\\": - self.smart_backspace_event(event) - text.see("insert") - return "break" + text.undo_block_start() + try: + if first and last: + text.delete(first, last) + text.mark_set("insert", first) + line = text.get("insert linestart", "insert") + i, n = 0, len(line) + while i < n and line[i] in " \t": + i = i+1 + indent = line[:i] + # strip trailing whitespace + i = 0 + while line and line[-1] in " \t": + line = line[:-1] + i = i + 1 + if i: + text.delete("insert - %d chars" % i, "insert") + text.insert("insert", "\n" + indent) + if _is_block_opener(line): + self.smart_indent_event(event) + elif indent and _is_block_closer(line) and line[-1:] != "\\": + self.smart_backspace_event(event) + text.see("insert") + return "break" + finally: + text.undo_block_stop() auto_indent = newline_and_indent_event @@ -275,8 +283,10 @@ class AutoIndent: return text.tag_remove("sel", "1.0", "end") text.mark_set("insert", head) + text.undo_block_start() text.delete(head, tail) text.insert(head, newchars) + text.undo_block_stop() text.tag_add("sel", head, "insert") def tabify(line, tabsize=8): diff --git a/Tools/idle/EditorWindow.py b/Tools/idle/EditorWindow.py index 2ae69cfa8a8..8b6a0b405c9 100644 --- a/Tools/idle/EditorWindow.py +++ b/Tools/idle/EditorWindow.py @@ -147,6 +147,8 @@ class EditorWindow: self.undo = undo = self.UndoDelegator(); per.insertfilter(undo) self.io = io = self.IOBinding(self) + text.undo_block_start = undo.undo_block_start + text.undo_block_stop = undo.undo_block_stop undo.set_saved_change_hook(self.saved_change_hook) io.set_filename_change_hook(self.filename_change_hook) diff --git a/Tools/idle/FormatParagraph.py b/Tools/idle/FormatParagraph.py index f8827e76158..e17f54c51e6 100644 --- a/Tools/idle/FormatParagraph.py +++ b/Tools/idle/FormatParagraph.py @@ -37,8 +37,10 @@ class FormatParagraph: text.tag_remove("sel", "1.0", "end") if newdata != data: text.mark_set("insert", first) + text.undo_block_start() text.delete(first, last) text.insert(first, newdata) + text.undo_block_stop() else: text.mark_set("insert", last) text.see("insert") diff --git a/Tools/idle/UndoDelegator.py b/Tools/idle/UndoDelegator.py index 39c8e634665..ec7af81bcd8 100644 --- a/Tools/idle/UndoDelegator.py +++ b/Tools/idle/UndoDelegator.py @@ -49,6 +49,7 @@ class UndoDelegator(Delegator): self.was_saved = -1 self.pointer = 0 self.undolist = [] + self.undoblock = 0 # or a CommandSequence instance self.set_saved(1) def set_saved(self, flag): @@ -82,8 +83,40 @@ class UndoDelegator(Delegator): def delete(self, index1, index2=None): self.addcmd(DeleteCommand(index1, index2)) - def addcmd(self, cmd): - cmd.do(self.delegate) + # 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.undoblock + self.undoblock = 0 + if len(cmd) > 0: + if len(cmd) == 1: + # no need to wrap a single cmd + cmd = cmd.getcmd(0) + # this blk of cmds, or single cmd, has already + # been done, so don't execute it again + self.addcmd(cmd, 0) + + def addcmd(self, cmd, execute=1): + if execute: + cmd.do(self.delegate) + if self.undoblock != 0: + self.undoblock.append(cmd) + return if self.can_merge and self.pointer > 0: lastcmd = self.undolist[self.pointer-1] if lastcmd.merge(cmd): @@ -264,6 +297,44 @@ class DeleteCommand(Command): 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 unit + + def __init__(self): + self.cmds = [] + self.depth = 0 + + def __repr__(self): + s = self.__class__.__name__ + strs = [] + for cmd in self.cmds: + strs.append(" " + `cmd`) + return s + "(\n" + string.join(strs, ",\n") + "\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 + incr + return self.depth def main(): from Percolator import Percolator