4 from Delegator
import Delegator
14 #$ event <<dump-undo-state>>
15 #$ win <Control-backslash>
16 #$ unix <Control-backslash>
19 class UndoDelegator(Delegator
):
24 Delegator
.__init
__(self
)
27 def setdelegate(self
, delegate
):
28 if self
.delegate
is not None:
29 self
.unbind("<<undo>>")
30 self
.unbind("<<redo>>")
31 self
.unbind("<<dump-undo-state>>")
32 Delegator
.setdelegate(self
, delegate
)
33 if delegate
is not None:
34 self
.bind("<<undo>>", self
.undo_event
)
35 self
.bind("<<redo>>", self
.redo_event
)
36 self
.bind("<<dump-undo-state>>", self
.dump_event
)
38 def dump_event(self
, event
):
39 from pprint
import pprint
40 pprint(self
.undolist
[:self
.pointer
])
41 print "pointer:", self
.pointer
,
42 print "saved:", self
.saved
,
43 print "can_merge:", self
.can_merge
,
44 print "get_saved():", self
.get_saved()
45 pprint(self
.undolist
[self
.pointer
:])
54 def set_saved(self
, flag
):
56 self
.saved
= self
.pointer
63 return self
.saved
== self
.pointer
65 saved_change_hook
= None
67 def set_saved_change_hook(self
, hook
):
68 self
.saved_change_hook
= hook
72 def check_saved(self
):
73 is_saved
= self
.get_saved()
74 if is_saved
!= self
.was_saved
:
75 self
.was_saved
= is_saved
76 if self
.saved_change_hook
:
77 self
.saved_change_hook()
79 def insert(self
, index
, chars
, tags
=None):
80 self
.addcmd(InsertCommand(index
, chars
, tags
))
82 def delete(self
, index1
, index2
=None):
83 self
.addcmd(DeleteCommand(index1
, index2
))
85 def addcmd(self
, cmd
):
87 if self
.can_merge
and self
.pointer
> 0:
88 lastcmd
= self
.undolist
[self
.pointer
-1]
89 if lastcmd
.merge(cmd
):
91 self
.undolist
[self
.pointer
:] = [cmd
]
92 if self
.saved
> self
.pointer
:
94 self
.pointer
= self
.pointer
+ 1
95 if len(self
.undolist
) > self
.max_undo
:
96 ##print "truncating undo list"
98 self
.pointer
= self
.pointer
- 1
100 self
.saved
= self
.saved
- 1
104 def undo_event(self
, event
):
105 if self
.pointer
== 0:
108 cmd
= self
.undolist
[self
.pointer
- 1]
109 cmd
.undo(self
.delegate
)
110 self
.pointer
= self
.pointer
- 1
115 def redo_event(self
, event
):
116 if self
.pointer
>= len(self
.undolist
):
119 cmd
= self
.undolist
[self
.pointer
]
120 cmd
.redo(self
.delegate
)
121 self
.pointer
= self
.pointer
+ 1
129 # Base class for Undoable commands
133 def __init__(self
, index1
, index2
, chars
, tags
=None):
134 self
.marks_before
= {}
135 self
.marks_after
= {}
143 s
= self
.__class
__.__name
__
144 t
= (self
.index1
, self
.index2
, self
.chars
, self
.tags
)
145 if self
.tags
is None:
152 def redo(self
, text
):
155 def undo(self
, text
):
158 def merge(self
, cmd
):
161 def save_marks(self
, text
):
163 for name
in text
.mark_names():
164 if name
!= "insert" and name
!= "current":
165 marks
[name
] = text
.index(name
)
168 def set_marks(self
, text
, marks
):
169 for name
, index
in marks
.items():
170 text
.mark_set(name
, index
)
173 class InsertCommand(Command
):
175 # Undoable insert command
177 def __init__(self
, index1
, chars
, tags
=None):
178 Command
.__init
__(self
, index1
, None, chars
, tags
)
181 self
.marks_before
= self
.save_marks(text
)
182 self
.index1
= text
.index(self
.index1
)
183 if text
.compare(self
.index1
, ">", "end-1c"):
184 # Insert before the final newline
185 self
.index1
= text
.index("end-1c")
186 text
.insert(self
.index1
, self
.chars
, self
.tags
)
187 self
.index2
= text
.index("%s+%dc" % (self
.index1
, len(self
.chars
)))
188 self
.marks_after
= self
.save_marks(text
)
189 ##sys.__stderr__.write("do: %s\n" % self)
191 def redo(self
, text
):
192 text
.mark_set('insert', self
.index1
)
193 text
.insert(self
.index1
, self
.chars
, self
.tags
)
194 self
.set_marks(text
, self
.marks_after
)
196 ##sys.__stderr__.write("redo: %s\n" % self)
198 def undo(self
, text
):
199 text
.mark_set('insert', self
.index1
)
200 text
.delete(self
.index1
, self
.index2
)
201 self
.set_marks(text
, self
.marks_before
)
203 ##sys.__stderr__.write("undo: %s\n" % self)
205 def merge(self
, cmd
):
206 if self
.__class
__ is not cmd
.__class
__:
208 if self
.index2
!= cmd
.index1
:
210 if self
.tags
!= cmd
.tags
:
212 if len(cmd
.chars
) != 1:
215 self
.classify(self
.chars
[-1]) != self
.classify(cmd
.chars
):
217 self
.index2
= cmd
.index2
218 self
.chars
= self
.chars
+ cmd
.chars
221 alphanumeric
= string
.letters
+ string
.digits
+ "_"
223 def classify(self
, c
):
224 if c
in self
.alphanumeric
:
225 return "alphanumeric"
231 class DeleteCommand(Command
):
233 # Undoable delete command
235 def __init__(self
, index1
, index2
=None):
236 Command
.__init
__(self
, index1
, index2
, None, None)
239 self
.marks_before
= self
.save_marks(text
)
240 self
.index1
= text
.index(self
.index1
)
242 self
.index2
= text
.index(self
.index2
)
244 self
.index2
= text
.index(self
.index1
+ " +1c")
245 if text
.compare(self
.index2
, ">", "end-1c"):
246 # Don't delete the final newline
247 self
.index2
= text
.index("end-1c")
248 self
.chars
= text
.get(self
.index1
, self
.index2
)
249 text
.delete(self
.index1
, self
.index2
)
250 self
.marks_after
= self
.save_marks(text
)
251 ##sys.__stderr__.write("do: %s\n" % self)
253 def redo(self
, text
):
254 text
.mark_set('insert', self
.index1
)
255 text
.delete(self
.index1
, self
.index2
)
256 self
.set_marks(text
, self
.marks_after
)
258 ##sys.__stderr__.write("redo: %s\n" % self)
260 def undo(self
, text
):
261 text
.mark_set('insert', self
.index1
)
262 text
.insert(self
.index1
, self
.chars
)
263 self
.set_marks(text
, self
.marks_before
)
265 ##sys.__stderr__.write("undo: %s\n" % self)
269 from Percolator
import Percolator
271 root
.wm_protocol("WM_DELETE_WINDOW", root
.quit
)
280 if __name__
== "__main__":