summaryrefslogtreecommitdiff
path: root/undo.cc
blob: d8b9dea4e0191e3fb731cda0683cff51888ed41a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.

#include "undo.h"
#include "dbg.h"

#define DEFAULT_LIMIT	4000
#define RESERVE		1000

UndoStack::UndoStack()
{
    top = 0;
    merge_small_ops = false;
    bytes_size = 0;
    bytes_size_limit = DEFAULT_LIMIT;
    truncated = false;
}

void UndoStack::clear()
{
    stack.clear();
    bytes_size = 0;
    top = 0;
    truncated = false;
}

// get_prev_op() - returns a pointer to the last operation made to the buffer.
// returns NULL if the undo stack is empty.

UndoOp* UndoStack::get_prev_op()
{
    if (top == 0)
	return NULL;
    return &stack[--top];
}

// get_next_op() - returns a pointer to the last operation that was undone.

UndoOp *UndoStack::get_next_op()
{
    if (top == stack.size())
	return NULL;
    return &stack[top++];
}

void UndoStack::set_size_limit(size_t limit)
{
    bytes_size_limit = limit;
    clear();
}

void UndoStack::update_size_up(int chars_count)
{
    bytes_size += chars_count * sizeof(unichar);
}

void UndoStack::update_size_up(const UndoOp &op)
{
    bytes_size += op.calc_size();
}

void UndoStack::update_size_down(const UndoOp &op)
{
    bytes_size -= op.calc_size();
}

// truncate_undo() - called when the stack size is too big. it erases the
// old operations.

void UndoStack::truncate_undo()
{
    unsigned new_size = bytes_size_limit;
    // we have to reserve some space in order not to reach the
    // size-limit very soon.
    if (new_size > RESERVE)
	new_size -= RESERVE;
    else
	new_size = 0;
	    
    unsigned i = 0;
    while (i < stack.size() && bytes_size > new_size) {
	update_size_down(stack[i++]);
    }
    stack.erase(stack.begin(), stack.begin() + i);
    top -= i;

    truncated = true;
}

// merge() - merges a new operation with the last operation. returns false if
// that was not possible.
//
// An example: suppose the last operation was to insert "a" into the buffer.
// If the user now types "b", merge() will change the last operation to record
// an insertion of "ab".
//
// However, if the user moves the cursor to a different location before typing
// "b", such a merge is not possible, because "a" and "b" are not adjacent.

bool UndoOp::merge(const UndoOp &op)
{
    if (type != op.type)
	return false;
    if (op.inserted_text.len() + op.deleted_text.len() != 1)
	return false;
    if (point.para != op.point.para)
	return false;
  
    switch (type) {
    case opInsert:
	if (op.point.pos == point.pos + inserted_text.len()) {
	    inserted_text.append(op.inserted_text);
	    return true;
	}
	break;
    case opDelete:
	if (op.point.pos == point.pos) {
	    deleted_text.append(op.deleted_text);
	    return true;
	}
	if (op.point.pos == point.pos - 1) {
	    point.pos--;
	    deleted_text.insert(deleted_text.begin(),
				op.deleted_text.begin(),
				op.deleted_text.end());
	    return true;
	}
	break;
    default:
	break;
    }

    return false;
}

// erase_redo_ops() - erases the operations that were undone.

void UndoStack::erase_redo_ops()
{
    for (unsigned i = top; i < stack.size(); i++)
	update_size_down(stack[i]);
    stack.erase(stack.begin() + top, stack.end());
}

// record_op() - records an operation on the stack.

void UndoStack::record_op(const UndoOp &op)
{
    if (disabled())
	return;

    // recording an opearation erases the recordings of all the operations
    // that were undone till now.
    if (is_redo_available())
	erase_redo_ops();

    if (undo_size_too_big())
	truncate_undo();

    // first try to merge this operation with the previous one. if that
    // fails, record it as a separate operation.
    if (merge_small_ops && !stack.empty() && stack.back().merge(op)) {
	update_size_up(1); // one character was recorded
    } else {
	stack.push_back(op);
	update_size_up(op);
	top++;
    }
}