~ubuntu-branches/ubuntu/trusty/reinteract/trusty

« back to all changes in this revision

Viewing changes to lib/reinteract/undo_stack.py

  • Committer: Bazaar Package Importer
  • Author(s): Chris Lamb
  • Date: 2009-03-28 00:53:14 UTC
  • Revision ID: james.westby@ubuntu.com-20090328005314-ramzoo0q6r8rmwuc
Tags: upstream-0.5.0
ImportĀ upstreamĀ versionĀ 0.5.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2007 Owen Taylor
 
2
#
 
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.
 
6
#
 
7
########################################################################
 
8
 
 
9
import re
 
10
 
 
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)$')
 
16
 
 
17
class _InsertDeleteOp(object):
 
18
    def __init__(self, start, end, text):
 
19
        self.start = start
 
20
        self.end = end
 
21
        self.text = text
 
22
 
 
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])
 
28
 
 
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])
 
34
 
 
35
class InsertOp(_InsertDeleteOp):
 
36
    def redo(self, worksheet):
 
37
        self._insert(worksheet)
 
38
        
 
39
    def undo(self, worksheet):
 
40
        self._delete(worksheet)
 
41
 
 
42
    def __repr__(self):
 
43
        return "InsertOp(%s, %s, %s)" % (self.start, self.end, repr(self.text))
 
44
 
 
45
class DeleteOp(_InsertDeleteOp):
 
46
    def redo(self, worksheet):
 
47
        self._delete(worksheet)
 
48
        
 
49
    def undo(self, worksheet):
 
50
        self._insert(worksheet)
 
51
 
 
52
    def __repr__(self):
 
53
        return "DeleteOp(%s, %s, %s)" % (self.start, self.end, repr(self.text))
 
54
    
 
55
class BeginActionOp(object):
 
56
    def __repr__(self):
 
57
        return "BeginActionOp()"
 
58
    
 
59
class EndActionOp(object):
 
60
    def __repr__(self):
 
61
        return "EndActionOp()"
 
62
    
 
63
class UndoStack(object):
 
64
    def __init__(self, worksheet):
 
65
        self.__worksheet = worksheet
 
66
        self.__position = 0
 
67
        # The position at which we last pruned the stack; everything after
 
68
        # this has been inserted consecutively without any intervening
 
69
        # undos and redos
 
70
        self.__prune_position = 0
 
71
        self.__stack = []
 
72
        self.__applying_undo = False
 
73
        self.__user_action_count = 0
 
74
        self.__action_ops = 0
 
75
 
 
76
    def undo(self):
 
77
        if self.__position == 0:
 
78
            return
 
79
 
 
80
        self.__position -= 1
 
81
        
 
82
        self.__applying_undo = True
 
83
        try:
 
84
            if isinstance(self.__stack[self.__position], EndActionOp):
 
85
                self.__position -= 1
 
86
                while not isinstance(self.__stack[self.__position], BeginActionOp):
 
87
                    self.__stack[self.__position].undo(self.__worksheet)
 
88
                    self.__position -= 1
 
89
            else:
 
90
                self.__stack[self.__position].undo(self.__worksheet)
 
91
        finally:
 
92
            self.__applying_undo = False
 
93
 
 
94
    def redo(self):
 
95
        if self.__position == len(self.__stack):
 
96
            return
 
97
 
 
98
        self.__position += 1
 
99
        self.__applying_undo = True
 
100
        try:
 
101
            if isinstance(self.__stack[self.__position - 1], BeginActionOp):
 
102
                self.__position += 1
 
103
                while not isinstance(self.__stack[self.__position - 1], EndActionOp):
 
104
                    self.__stack[self.__position - 1].redo(self.__worksheet)
 
105
                    self.__position += 1
 
106
            else:
 
107
                self.__stack[self.__position - 1].redo(self.__worksheet)
 
108
        finally:
 
109
            self.__applying_undo = False
 
110
 
 
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:
 
115
            return
 
116
 
 
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):
 
121
            prev.end = cur.end
 
122
            prev.text += cur.text
 
123
            self.__stack.pop()
 
124
            self.__position -= 1
 
125
            
 
126
    def append_op(self, op):
 
127
        if self.__applying_undo:
 
128
            return
 
129
 
 
130
        if self.__position < len(self.__stack):
 
131
            assert self.__action_ops == 0
 
132
            self.__stack[self.__position:] = []
 
133
            self.__prune_position = self.__position
 
134
 
 
135
        self.__stack.append(op)
 
136
        self.__position += 1
 
137
        
 
138
        if self.__user_action_count > 0:
 
139
            self.__action_ops += 1
 
140
        else:
 
141
            self.__check_coalesce()
 
142
        
 
143
    def begin_user_action(self):
 
144
        self.__user_action_count += 1
 
145
        
 
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())
 
152
                self.__position += 2
 
153
            elif self.__action_ops == 1:
 
154
                self.__check_coalesce()
 
155
            self.__action_ops = 0
 
156
                
 
157
 
 
158
    def clear(self):
 
159
        self.__stack = []
 
160
        self.__position = 0
 
161
 
 
162
    def __repr__(self):
 
163
        return "UndoStack(stack=%s, position=%d)" % (self.__stack, self.__position)
 
164