]> git.lyx.org Git - features.git/blob - src/changes.C
0ba57aadf07481dbcaa167e2c6e0126662a8c207
[features.git] / src / changes.C
1 /**
2  * \file changes.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author John Levon
7  * \author Michael Gerz
8  *
9  * Full author contact details are available in file CREDITS.
10  *
11  * Record changes in a paragraph.
12  */
13
14 #include <config.h>
15
16 #include "changes.h"
17 #include "debug.h"
18
19 #include <boost/assert.hpp>
20
21
22 namespace lyx {
23
24 using std::endl;
25 using std::string;
26 using std::max;
27
28 /*
29  * Class Change has a changetime field that specifies the exact time at which
30  * a specific change was made. The change time is used as a guidance for the
31  * user while editing his document. Presently, it is not considered for LaTeX
32  * export. To avoid that every keystroke results in a separate change, a 
33  * tolerance interval of 5 minutes is used. That means if there are two adjacent
34  * changes that only differ in their change time with abs(ct1 - ct2) < 300 sec,
35  * they will be merged (and the later change time is preserved).
36  * Technically, the check for equality (or similarity) is made in operator==(...).
37  * The merging of similar changes happens in method merge().
38  */
39
40 bool operator==(Change const & l, Change const & r)
41 {
42         if (l.type != r.type) {
43                 return false;
44         }
45
46         if (l.type == Change::UNCHANGED) {
47                 return true;
48         }
49
50         return l.author == r.author
51                // both changes made within 5 minutes?
52                && abs(l.changetime - r.changetime) < 300;
53 }
54
55
56 bool operator!=(Change const & l, Change const & r)
57 {
58         return !(l == r);
59 }
60
61
62 bool operator==(Changes::Range const & r1, Changes::Range const & r2)
63 {
64         return r1.start == r2.start && r1.end == r2.end;
65 }
66
67
68 bool operator!=(Changes::Range const & r1, Changes::Range const & r2)
69 {
70         return !(r1 == r2);
71 }
72
73
74 bool Changes::Range::contains(Range const & r) const
75 {
76         return r.start >= start && r.end <= end;
77 }
78
79
80 bool Changes::Range::contains(pos_type const pos) const
81 {
82         return pos >= start && pos < end;
83 }
84
85
86 bool Changes::Range::intersects(Range const & r) const
87 {
88         return r.start < end && r.end > start; // end itself is not in the range!
89 }
90
91
92 void Changes::set(Change const & change, pos_type const pos)
93 {
94         set(change, pos, pos + 1);
95 }
96
97
98 void Changes::set(Change const & change, pos_type const start, pos_type const end)
99 {
100         if (lyxerr.debugging(Debug::CHANGES)) {
101                 lyxerr[Debug::CHANGES] << "setting change (type: " << change.type
102                         << ", author: " << change.author << ", time: " << change.changetime
103                         << ") in range (" << start << ", " << end << ")" << endl;
104         }
105
106         Range const newRange(start, end);
107
108         ChangeTable::iterator it = table_.begin();
109
110         for (; it != table_.end(); ) {
111                 // current change starts like or follows new change
112                 if (it->range.start >= start) {
113                         break;
114                 }
115
116                 // new change intersects with existing change
117                 if (it->range.end > start) {
118                         pos_type oldEnd = it->range.end;
119                         it->range.end = start;
120                         if (lyxerr.debugging(Debug::CHANGES)) {
121                                 lyxerr[Debug::CHANGES] << "  cutting tail of type " << it->change.type
122                                         << " resulting in range (" << it->range.start << ", "
123                                         << it->range.end << ")" << endl;
124                         }
125                         ++it;
126                         if (oldEnd >= end) {
127                                 if (lyxerr.debugging(Debug::CHANGES)) {
128                                         lyxerr[Debug::CHANGES] << "  inserting tail in range ("
129                                                 << end << ", " << oldEnd << ")" << endl;
130                                 }
131                                 it = table_.insert(it, ChangeRange((it-1)->change, Range(end, oldEnd)));
132                         }
133                         continue;
134                 }
135
136                 ++it;
137         }
138
139         if (change.type != Change::UNCHANGED) {
140                 if (lyxerr.debugging(Debug::CHANGES)) {
141                         lyxerr[Debug::CHANGES] << "  inserting change" << endl;
142                 }
143                 it = table_.insert(it, ChangeRange(change, Range(start, end)));
144                 ++it;
145         }
146
147         for (; it != table_.end(); ) {
148                 // new change 'contains' existing change
149                 if (newRange.contains(it->range)) {
150                         if (lyxerr.debugging(Debug::CHANGES)) {
151                                 lyxerr[Debug::CHANGES] << "  removing subrange ("
152                                         << it->range.start << ", " << it->range.end << ")" << endl;
153                         }
154                         it = table_.erase(it);
155                         continue;
156                 }
157
158                 // new change precedes existing change
159                 if (it->range.start >= end) {
160                         break;
161                 }
162
163                 // new change intersects with existing change
164                 it->range.start = end;
165                 if (lyxerr.debugging(Debug::CHANGES)) {
166                         lyxerr[Debug::CHANGES] << "  cutting head of type "
167                                 << it->change.type << " resulting in range ("
168                                 << end << ", " << it->range.end << ")" << endl;
169                 }
170                 break; // no need for another iteration
171         }
172
173         merge();
174 }
175
176
177 void Changes::erase(pos_type const pos)
178 {
179         if (lyxerr.debugging(Debug::CHANGES)) {
180                 lyxerr[Debug::CHANGES] << "Erasing change at position " << pos << endl;
181         }
182
183         ChangeTable::iterator it = table_.begin();
184         ChangeTable::iterator end = table_.end();
185
186         for (; it != end; ++it) {
187                 // range (pos,pos+x) becomes (pos,pos+x-1)
188                 if (it->range.start > pos) {
189                         --(it->range.start);
190                 }
191                 // range (pos-x,pos) stays (pos-x,pos)
192                 if (it->range.end > pos) {
193                         --(it->range.end);
194                 }
195         }
196
197         merge();
198 }
199
200
201 void Changes::insert(Change const & change, lyx::pos_type pos)
202 {
203         if (lyxerr.debugging(Debug::CHANGES)) {
204                 lyxerr[Debug::CHANGES] << "Inserting change of type " << change.type
205                         << " at position " << pos << endl;
206         }
207
208         ChangeTable::iterator it = table_.begin();
209         ChangeTable::iterator end = table_.end();
210
211         for (; it != end; ++it) {
212                 // range (pos,pos+x) becomes (pos+1,pos+x+1)
213                 if (it->range.start >= pos) {
214                         ++(it->range.start);
215                 }
216
217                 // range (pos-x,pos) stays as it is
218                 if (it->range.end > pos) {
219                         ++(it->range.end);
220                 }
221         }
222
223         set(change, pos, pos + 1); // set will call merge
224 }
225
226
227 Change const Changes::lookup(pos_type const pos) const
228 {
229         if (table_.empty()) {
230                 return Change(Change::UNCHANGED);
231         }
232         ChangeTable::const_iterator it = table_.begin();
233         ChangeTable::const_iterator const end = table_.end();
234
235         for (; it != end; ++it) {
236                 if (it->range.contains(pos))
237                         return it->change;
238         }
239
240         return Change(Change::UNCHANGED);
241 }
242
243
244 bool Changes::isChanged(pos_type const start, pos_type const end) const
245 {
246         ChangeTable::const_iterator it = table_.begin();
247         ChangeTable::const_iterator const itend = table_.end();
248
249         for (; it != itend; ++it) {
250                 if (it->range.intersects(Range(start, end))) {
251                         if (lyxerr.debugging(Debug::CHANGES)) {
252                                 lyxerr[Debug::CHANGES] << "found intersection of range ("
253                                         << start << ", " << end << ") with ("
254                                         << it->range.start << ", " << it->range.end
255                                         << ") of type " << it->change.type << endl;
256                         }
257                         return true;
258                 }
259         }
260         return false;
261 }
262
263
264 void Changes::merge()
265 {
266         if (lyxerr.debugging(Debug::CHANGES)) {
267                 lyxerr[Debug::CHANGES] << "merging changes..." << endl;
268         }
269
270         ChangeTable::iterator it = table_.begin();
271
272         while (it != table_.end()) {
273                 if (lyxerr.debugging(Debug::CHANGES)) {
274                         lyxerr[Debug::CHANGES] << "  found change of type " << it->change.type
275                                 << " and range (" << it->range.start << ", " << it->range.end
276                                 << ")" << endl;
277                 }
278
279                 if (it->range.start == it->range.end) {
280                         if (lyxerr.debugging(Debug::CHANGES)) {
281                                 lyxerr[Debug::CHANGES] << "  removing empty range for pos "
282                                         << it->range.start << endl;
283                         }
284
285                         table_.erase(it);
286                         // start again
287                         it = table_.begin();
288                         continue;
289                 }
290
291                 if (it + 1 == table_.end())
292                         break;
293
294                 if (it->change == (it + 1)->change && it->range.end == (it + 1)->range.start) {
295                         if (lyxerr.debugging(Debug::CHANGES)) {
296                                 lyxerr[Debug::CHANGES] << "  merging ranges (" << it->range.start << ", "
297                                         << it->range.end << ") and (" << (it + 1)->range.start << ", "
298                                         << (it + 1)->range.end << ")" << endl;
299                         }
300                         (it + 1)->range.start = it->range.start;
301                         (it + 1)->change.changetime = max(it->change.changetime,
302                                                           (it + 1)->change.changetime);
303                         table_.erase(it);
304                         // start again
305                         it = table_.begin();
306                         continue;
307                 }
308
309                 ++it;
310         }
311 }
312
313
314 int Changes::latexMarkChange(odocstream & os,
315                              Change::Type const oldChangeType, Change::Type const changeType,
316                              bool const & output)
317 {
318         if (!output || oldChangeType == changeType)
319                 return 0;
320
321         static docstring const start(from_ascii("\\changestart{}"));
322         static docstring const end(from_ascii("\\changeend{}"));
323         static docstring const son(from_ascii("\\overstrikeon{}"));
324         static docstring const soff(from_ascii("\\overstrikeoff{}"));
325
326         int column = 0;
327
328         if (oldChangeType == Change::DELETED) {
329                 os << soff;
330                 column += soff.length();
331         }
332
333         switch (changeType) {
334                 case Change::UNCHANGED:
335                         os << end;
336                         column += end.length();
337                         break;
338
339                 case Change::DELETED:
340                         if (oldChangeType == Change::UNCHANGED) {
341                                 os << start;
342                                 column += start.length();
343                         }
344                         os << son;
345                         column += son.length();
346                         break;
347
348                 case Change::INSERTED:
349                         if (oldChangeType == Change::UNCHANGED) {
350                                 os << start;
351                                 column += start.length();
352                         }
353                         break;
354         }
355
356         return column;
357 }
358
359
360 void Changes::lyxMarkChange(std::ostream & os, int & column,
361                             Change const & old, Change const & change)
362 {
363         if (old == change)
364                 return;
365
366         column = 0;
367
368         switch (change.type) {
369                 case Change::UNCHANGED:
370                         os << "\n\\change_unchanged\n";
371                         break;
372
373                 case Change::DELETED: {
374                         os << "\n\\change_deleted " << change.author
375                                 << " " << change.changetime << "\n";
376                         break;
377                 }
378
379                 case Change::INSERTED: {
380                         os << "\n\\change_inserted " << change.author
381                                 << " " << change.changetime << "\n";
382                         break;
383                 }
384         }
385 }
386
387
388 } // namespace lyx