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,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):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue