1
# Copyright 2007 Owen Taylor
3
# This file is part of Reinteract and distributed under the terms
4
# of the BSD license. See the file COPYING in the Reinteract
5
# distribution for full details.
7
########################################################################
11
# Two consecutive inserts are merged together if the sum of the
12
# two matches this. The (?!\n) is to defeat the normal regular
13
# expression behavior where 'a$' matches 'a\n' because $ matches
14
# before the last newline in the string
15
COALESCE_RE = re.compile(r'^\S+ *(?!\n)$')
17
class _InsertDeleteOp(object):
18
def __init__(self, start, end, text):
23
def _insert(self, worksheet):
24
worksheet.begin_user_action()
25
worksheet.insert(self.start[0], self.start[1], self.text)
26
worksheet.end_user_action()
27
worksheet.place_cursor(self.end[0], self.end[1])
29
def _delete(self, worksheet):
30
worksheet.begin_user_action()
31
worksheet.delete_range(self.start[0], self.start[1], self.end[0], self.end[1])
32
worksheet.end_user_action()
33
worksheet.place_cursor(self.start[0], self.start[1])
35
class InsertOp(_InsertDeleteOp):
36
def redo(self, worksheet):
37
self._insert(worksheet)
39
def undo(self, worksheet):
40
self._delete(worksheet)
43
return "InsertOp(%s, %s, %s)" % (self.start, self.end, repr(self.text))
45
class DeleteOp(_InsertDeleteOp):
46
def redo(self, worksheet):
47
self._delete(worksheet)
49
def undo(self, worksheet):
50
self._insert(worksheet)
53
return "DeleteOp(%s, %s, %s)" % (self.start, self.end, repr(self.text))
55
class BeginActionOp(object):
57
return "BeginActionOp()"
59
class EndActionOp(object):
61
return "EndActionOp()"
63
class UndoStack(object):
64
def __init__(self, worksheet):
65
self.__worksheet = worksheet
67
# The position at which we last pruned the stack; everything after
68
# this has been inserted consecutively without any intervening
70
self.__prune_position = 0
72
self.__applying_undo = False
73
self.__user_action_count = 0
77
if self.__position == 0:
82
self.__applying_undo = True
84
if isinstance(self.__stack[self.__position], EndActionOp):
86
while not isinstance(self.__stack[self.__position], BeginActionOp):
87
self.__stack[self.__position].undo(self.__worksheet)
90
self.__stack[self.__position].undo(self.__worksheet)
92
self.__applying_undo = False
95
if self.__position == len(self.__stack):
99
self.__applying_undo = True
101
if isinstance(self.__stack[self.__position - 1], BeginActionOp):
103
while not isinstance(self.__stack[self.__position - 1], EndActionOp):
104
self.__stack[self.__position - 1].redo(self.__worksheet)
107
self.__stack[self.__position - 1].redo(self.__worksheet)
109
self.__applying_undo = False
111
def __check_coalesce(self):
112
assert self.__position == len(self.__stack)
113
# Don't coalesce two ops unless they are actually adjacent in time
114
if self.__position < self.__prune_position + 2:
117
cur = self.__stack[-1]
118
prev = self.__stack[-2]
119
if isinstance(cur, InsertOp) and isinstance(prev, InsertOp) and \
120
cur.start == prev.end and COALESCE_RE.match(prev.text + cur.text):
122
prev.text += cur.text
126
def append_op(self, op):
127
if self.__applying_undo:
130
if self.__position < len(self.__stack):
131
assert self.__action_ops == 0
132
self.__stack[self.__position:] = []
133
self.__prune_position = self.__position
135
self.__stack.append(op)
138
if self.__user_action_count > 0:
139
self.__action_ops += 1
141
self.__check_coalesce()
143
def begin_user_action(self):
144
self.__user_action_count += 1
146
def end_user_action(self):
147
self.__user_action_count -= 1
148
if self.__user_action_count == 0:
149
if self.__action_ops > 1:
150
self.__stack.insert(len(self.__stack) - self.__action_ops, BeginActionOp())
151
self.__stack.append(EndActionOp())
153
elif self.__action_ops == 1:
154
self.__check_coalesce()
155
self.__action_ops = 0
163
return "UndoStack(stack=%s, position=%d)" % (self.__stack, self.__position)