]> git.lyx.org Git - features.git/blob - src/Changes.cpp
e8caabe587d744a8792d94c7a35bccd39ed309c8
[features.git] / src / Changes.cpp
1 /**
2  * \file Changes.cpp
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 "Author.h"
18 #include "BufferParams.h"
19 #include "LaTeXFeatures.h"
20
21 #include "support/debug.h"
22
23 #include <boost/assert.hpp>
24
25 #include <ostream>
26
27 namespace lyx {
28
29 /*
30  * Class Change has a changetime field that specifies the exact time at which
31  * a specific change was made. The change time is used as a guidance for the
32  * user while editing his document. Presently, it is not considered for LaTeX
33  * export.
34  * When merging two adjacent changes, the changetime is not considered,
35  * only the equality of the change type and author is checked (in method
36  * isSimilarTo(...)). If two changes are in fact merged (in method merge()),
37  * the later change time is preserved.
38  */
39
40 bool Change::isSimilarTo(Change const & change)
41 {
42         if (type != change.type)
43                 return false;
44
45         if (type == Change::UNCHANGED)
46                 return true;
47
48         return author == change.author;
49 }
50
51
52 bool operator==(Change const & l, Change const & r)
53 {
54         if (l.type != r.type)
55                 return false;
56
57         // two changes of type UNCHANGED are always equal
58         if (l.type == Change::UNCHANGED)
59                 return true;
60
61         return l.author == r.author && l.changetime == r.changetime;
62 }
63
64
65 bool operator!=(Change const & l, Change const & r)
66 {
67         return !(l == r);
68 }
69
70
71 bool operator==(Changes::Range const & r1, Changes::Range const & r2)
72 {
73         return r1.start == r2.start && r1.end == r2.end;
74 }
75
76
77 bool operator!=(Changes::Range const & r1, Changes::Range const & r2)
78 {
79         return !(r1 == r2);
80 }
81
82
83 bool Changes::Range::intersects(Range const & r) const
84 {
85         return r.start < end && r.end > start; // end itself is not in the range!
86 }
87
88
89 void Changes::set(Change const & change, pos_type const pos)
90 {
91         set(change, pos, pos + 1);
92 }
93
94
95 void Changes::set(Change const & change, pos_type const start, pos_type const end)
96 {
97         if (change.type != Change::UNCHANGED) {
98                 LYXERR(Debug::CHANGES, "setting change (type: " << change.type
99                         << ", author: " << change.author << ", time: " << change.changetime
100                         << ") in range (" << start << ", " << end << ")");
101         }
102
103         Range const newRange(start, end);
104
105         ChangeTable::iterator it = table_.begin();
106
107         for (; it != table_.end(); ) {
108                 // current change starts like or follows new change
109                 if (it->range.start >= start) {
110                         break;
111                 }
112
113                 // new change intersects with existing change
114                 if (it->range.end > start) {
115                         pos_type oldEnd = it->range.end;
116                         it->range.end = start;
117
118                         LYXERR(Debug::CHANGES, "  cutting tail of type " << it->change.type
119                                 << " resulting in range (" << it->range.start << ", "
120                                 << it->range.end << ")");
121
122                         ++it;
123                         if (oldEnd >= end) {
124                                 LYXERR(Debug::CHANGES, "  inserting tail in range ("
125                                         << end << ", " << oldEnd << ")");
126                                 it = table_.insert(it, ChangeRange((it-1)->change, Range(end, oldEnd)));
127                         }
128                         continue;
129                 }
130
131                 ++it;
132         }
133
134         if (change.type != Change::UNCHANGED) {
135                 LYXERR(Debug::CHANGES, "  inserting change");
136                 it = table_.insert(it, ChangeRange(change, Range(start, end)));
137                 ++it;
138         }
139
140         for (; it != table_.end(); ) {
141                 // new change 'contains' existing change
142                 if (newRange.contains(it->range)) {
143                         LYXERR(Debug::CHANGES, "  removing subrange ("
144                                 << it->range.start << ", " << it->range.end << ")");
145                         it = table_.erase(it);
146                         continue;
147                 }
148
149                 // new change precedes existing change
150                 if (it->range.start >= end)
151                         break;
152
153                 // new change intersects with existing change
154                 it->range.start = end;
155                 LYXERR(Debug::CHANGES, "  cutting head of type "
156                         << it->change.type << " resulting in range ("
157                         << end << ", " << it->range.end << ")");
158                 break; // no need for another iteration
159         }
160
161         merge();
162 }
163
164
165 void Changes::erase(pos_type const pos)
166 {
167         LYXERR(Debug::CHANGES, "Erasing change at position " << pos);
168
169         ChangeTable::iterator it = table_.begin();
170         ChangeTable::iterator end = table_.end();
171
172         for (; it != end; ++it) {
173                 // range (pos,pos+x) becomes (pos,pos+x-1)
174                 if (it->range.start > pos)
175                         --(it->range.start);
176                 // range (pos-x,pos) stays (pos-x,pos)
177                 if (it->range.end > pos)
178                         --(it->range.end);
179         }
180
181         merge();
182 }
183
184
185 void Changes::insert(Change const & change, lyx::pos_type pos)
186 {
187         if (change.type != Change::UNCHANGED) {
188                 LYXERR(Debug::CHANGES, "Inserting change of type " << change.type
189                         << " at position " << pos);
190         }
191
192         ChangeTable::iterator it = table_.begin();
193         ChangeTable::iterator end = table_.end();
194
195         for (; it != end; ++it) {
196                 // range (pos,pos+x) becomes (pos+1,pos+x+1)
197                 if (it->range.start >= pos)
198                         ++(it->range.start);
199
200                 // range (pos-x,pos) stays as it is
201                 if (it->range.end > pos)
202                         ++(it->range.end);
203         }
204
205         set(change, pos, pos + 1); // set will call merge
206 }
207
208
209 Change const & Changes::lookup(pos_type const pos) const
210 {
211         static Change const noChange = Change(Change::UNCHANGED);
212
213         ChangeTable::const_iterator it = table_.begin();
214         ChangeTable::const_iterator const end = table_.end();
215
216         for (; it != end; ++it) {
217                 if (it->range.contains(pos))
218                         return it->change;
219         }
220
221         return noChange;
222 }
223
224
225 bool Changes::isChanged(pos_type const start, pos_type const end) const
226 {
227         ChangeTable::const_iterator it = table_.begin();
228         ChangeTable::const_iterator const itend = table_.end();
229
230         for (; it != itend; ++it) {
231                 if (it->range.intersects(Range(start, end))) {
232                         LYXERR(Debug::CHANGES, "found intersection of range ("
233                                 << start << ", " << end << ") with ("
234                                 << it->range.start << ", " << it->range.end
235                                 << ") of type " << it->change.type);
236                         return true;
237                 }
238         }
239         return false;
240 }
241
242
243 void Changes::merge()
244 {
245         ChangeTable::iterator it = table_.begin();
246
247         while (it != table_.end()) {
248                 LYXERR(Debug::CHANGES, "found change of type " << it->change.type
249                         << " and range (" << it->range.start << ", " << it->range.end
250                         << ")");
251
252                 if (it->range.start == it->range.end) {
253                         LYXERR(Debug::CHANGES, "removing empty range for pos "
254                                 << it->range.start);
255
256                         table_.erase(it);
257                         // start again
258                         it = table_.begin();
259                         continue;
260                 }
261
262                 if (it + 1 == table_.end())
263                         break;
264
265                 if (it->change.isSimilarTo((it + 1)->change)
266                     && it->range.end == (it + 1)->range.start) {
267                         LYXERR(Debug::CHANGES, "merging ranges (" << it->range.start << ", "
268                                 << it->range.end << ") and (" << (it + 1)->range.start << ", "
269                                 << (it + 1)->range.end << ")");
270
271                         (it + 1)->range.start = it->range.start;
272                         (it + 1)->change.changetime = std::max(it->change.changetime,
273                                                           (it + 1)->change.changetime);
274                         table_.erase(it);
275                         // start again
276                         it = table_.begin();
277                         continue;
278                 }
279
280                 ++it;
281         }
282 }
283
284
285 int Changes::latexMarkChange(odocstream & os, BufferParams const & bparams,
286                              Change const & oldChange, Change const & change)
287 {
288         if (!bparams.outputChanges || oldChange == change)
289                 return 0;
290
291         int column = 0;
292
293         if (oldChange.type != Change::UNCHANGED) {
294                 os << '}'; // close \lyxadded or \lyxdeleted
295                 column++;
296         }
297
298         docstring chgTime;
299         chgTime += ctime(&change.changetime);
300         chgTime.erase(chgTime.end() - 1); // remove trailing '\n'
301
302         if (change.type == Change::DELETED) {
303                 docstring str = "\\lyxdeleted{" +
304                         bparams.authors().get(change.author).name() + "}{" +
305                         chgTime + "}{";
306                 os << str;
307                 column += str.size();
308         } else if (change.type == Change::INSERTED) {
309                 docstring str = "\\lyxadded{" +
310                         bparams.authors().get(change.author).name() + "}{" +
311                         chgTime + "}{";
312                 os << str;
313                 column += str.size();
314         }
315
316         return column;
317 }
318
319
320 void Changes::lyxMarkChange(std::ostream & os, int & column,
321                             Change const & old, Change const & change)
322 {
323         if (old == change)
324                 return;
325
326         column = 0;
327
328         switch (change.type) {
329                 case Change::UNCHANGED:
330                         os << "\n\\change_unchanged\n";
331                         break;
332
333                 case Change::DELETED: {
334                         os << "\n\\change_deleted " << change.author
335                                 << " " << change.changetime << "\n";
336                         break;
337                 }
338
339                 case Change::INSERTED: {
340                         os << "\n\\change_inserted " << change.author
341                                 << " " << change.changetime << "\n";
342                         break;
343                 }
344         }
345 }
346
347
348 void Changes::checkAuthors(AuthorList const & authorList)
349 {
350         ChangeTable::const_iterator it = table_.begin();
351         ChangeTable::const_iterator endit = table_.end();
352         for ( ; it != endit ; ++it) 
353                 if (it->change.type != Change::UNCHANGED)
354                         authorList.get(it->change.author).setUsed(true);
355 }
356
357 } // namespace lyx