summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>1999-05-03 15:49:52 (GMT)
committerGuido van Rossum <guido@python.org>1999-05-03 15:49:52 (GMT)
commit318a70d976fbf225de3485bbbc61b7fe1f24f4a2 (patch)
tree358d6940133c029256a0b4d94bde7ed61fba03e4
parent2d6a568a0f6c65c76797556bf949b97ede9049e1 (diff)
downloadcpython-318a70d976fbf225de3485bbbc61b7fe1f24f4a2.zip
cpython-318a70d976fbf225de3485bbbc61b7fe1f24f4a2.tar.gz
cpython-318a70d976fbf225de3485bbbc61b7fe1f24f4a2.tar.bz2
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 <wink>. 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.
-rw-r--r--Tools/idle/AutoIndent.py82
-rw-r--r--Tools/idle/EditorWindow.py2
-rw-r--r--Tools/idle/FormatParagraph.py2
-rw-r--r--Tools/idle/UndoDelegator.py75
4 files changed, 123 insertions, 38 deletions
diff --git a/Tools/idle/AutoIndent.py b/Tools/idle/AutoIndent.py
index 4de7ad0..386490d 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 2ae69cf..8b6a0b4 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 f8827e7..e17f54c 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 39c8e63..ec7af81 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