mirror of https://github.com/python/cpython
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.
This commit is contained in:
parent
2d6a568a0f
commit
318a70d976
|
@ -142,6 +142,8 @@ class AutoIndent:
|
||||||
last = text.index("sel.last")
|
last = text.index("sel.last")
|
||||||
except TclError:
|
except TclError:
|
||||||
first = last = None
|
first = last = None
|
||||||
|
text.undo_block_start()
|
||||||
|
try:
|
||||||
if first and last:
|
if first and last:
|
||||||
if index2line(first) != index2line(last):
|
if index2line(first) != index2line(last):
|
||||||
return self.indent_region_event(event)
|
return self.indent_region_event(event)
|
||||||
|
@ -156,6 +158,8 @@ class AutoIndent:
|
||||||
text.insert("insert", pad)
|
text.insert("insert", pad)
|
||||||
text.see("insert")
|
text.see("insert")
|
||||||
return "break"
|
return "break"
|
||||||
|
finally:
|
||||||
|
text.undo_block_stop()
|
||||||
|
|
||||||
def newline_and_indent_event(self, event):
|
def newline_and_indent_event(self, event):
|
||||||
text = self.text
|
text = self.text
|
||||||
|
@ -164,6 +168,8 @@ class AutoIndent:
|
||||||
last = text.index("sel.last")
|
last = text.index("sel.last")
|
||||||
except TclError:
|
except TclError:
|
||||||
first = last = None
|
first = last = None
|
||||||
|
text.undo_block_start()
|
||||||
|
try:
|
||||||
if first and last:
|
if first and last:
|
||||||
text.delete(first, last)
|
text.delete(first, last)
|
||||||
text.mark_set("insert", first)
|
text.mark_set("insert", first)
|
||||||
|
@ -186,6 +192,8 @@ class AutoIndent:
|
||||||
self.smart_backspace_event(event)
|
self.smart_backspace_event(event)
|
||||||
text.see("insert")
|
text.see("insert")
|
||||||
return "break"
|
return "break"
|
||||||
|
finally:
|
||||||
|
text.undo_block_stop()
|
||||||
|
|
||||||
auto_indent = newline_and_indent_event
|
auto_indent = newline_and_indent_event
|
||||||
|
|
||||||
|
@ -275,8 +283,10 @@ class AutoIndent:
|
||||||
return
|
return
|
||||||
text.tag_remove("sel", "1.0", "end")
|
text.tag_remove("sel", "1.0", "end")
|
||||||
text.mark_set("insert", head)
|
text.mark_set("insert", head)
|
||||||
|
text.undo_block_start()
|
||||||
text.delete(head, tail)
|
text.delete(head, tail)
|
||||||
text.insert(head, newchars)
|
text.insert(head, newchars)
|
||||||
|
text.undo_block_stop()
|
||||||
text.tag_add("sel", head, "insert")
|
text.tag_add("sel", head, "insert")
|
||||||
|
|
||||||
def tabify(line, tabsize=8):
|
def tabify(line, tabsize=8):
|
||||||
|
|
|
@ -147,6 +147,8 @@ class EditorWindow:
|
||||||
self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
|
self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
|
||||||
self.io = io = self.IOBinding(self)
|
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)
|
undo.set_saved_change_hook(self.saved_change_hook)
|
||||||
io.set_filename_change_hook(self.filename_change_hook)
|
io.set_filename_change_hook(self.filename_change_hook)
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,10 @@ class FormatParagraph:
|
||||||
text.tag_remove("sel", "1.0", "end")
|
text.tag_remove("sel", "1.0", "end")
|
||||||
if newdata != data:
|
if newdata != data:
|
||||||
text.mark_set("insert", first)
|
text.mark_set("insert", first)
|
||||||
|
text.undo_block_start()
|
||||||
text.delete(first, last)
|
text.delete(first, last)
|
||||||
text.insert(first, newdata)
|
text.insert(first, newdata)
|
||||||
|
text.undo_block_stop()
|
||||||
else:
|
else:
|
||||||
text.mark_set("insert", last)
|
text.mark_set("insert", last)
|
||||||
text.see("insert")
|
text.see("insert")
|
||||||
|
|
|
@ -49,6 +49,7 @@ class UndoDelegator(Delegator):
|
||||||
self.was_saved = -1
|
self.was_saved = -1
|
||||||
self.pointer = 0
|
self.pointer = 0
|
||||||
self.undolist = []
|
self.undolist = []
|
||||||
|
self.undoblock = 0 # or a CommandSequence instance
|
||||||
self.set_saved(1)
|
self.set_saved(1)
|
||||||
|
|
||||||
def set_saved(self, flag):
|
def set_saved(self, flag):
|
||||||
|
@ -82,8 +83,40 @@ class UndoDelegator(Delegator):
|
||||||
def delete(self, index1, index2=None):
|
def delete(self, index1, index2=None):
|
||||||
self.addcmd(DeleteCommand(index1, index2))
|
self.addcmd(DeleteCommand(index1, index2))
|
||||||
|
|
||||||
def addcmd(self, cmd):
|
# 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)
|
cmd.do(self.delegate)
|
||||||
|
if self.undoblock != 0:
|
||||||
|
self.undoblock.append(cmd)
|
||||||
|
return
|
||||||
if self.can_merge and self.pointer > 0:
|
if self.can_merge and self.pointer > 0:
|
||||||
lastcmd = self.undolist[self.pointer-1]
|
lastcmd = self.undolist[self.pointer-1]
|
||||||
if lastcmd.merge(cmd):
|
if lastcmd.merge(cmd):
|
||||||
|
@ -264,6 +297,44 @@ class DeleteCommand(Command):
|
||||||
text.see('insert')
|
text.see('insert')
|
||||||
##sys.__stderr__.write("undo: %s\n" % self)
|
##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():
|
def main():
|
||||||
from Percolator import Percolator
|
from Percolator import Percolator
|
||||||
|
|
Loading…
Reference in New Issue