3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
9 * Full author contact details are available in file CREDITS.
11 * Record changes in a paragraph.
18 #include "BufferParams.h"
19 #include "LaTeXFeatures.h"
21 #include "support/debug.h"
23 #include "support/lassert.h"
32 * Class Change has a changetime field that specifies the exact time at which
33 * a specific change was made. The change time is used as a guidance for the
34 * user while editing his document. Presently, it is not considered for LaTeX
36 * When merging two adjacent changes, the changetime is not considered,
37 * only the equality of the change type and author is checked (in method
38 * isSimilarTo(...)). If two changes are in fact merged (in method merge()),
39 * the later change time is preserved.
42 bool Change::isSimilarTo(Change
const & change
)
44 if (type
!= change
.type
)
47 if (type
== Change::UNCHANGED
)
50 return author
== change
.author
;
54 bool operator==(Change
const & l
, Change
const & r
)
59 // two changes of type UNCHANGED are always equal
60 if (l
.type
== Change::UNCHANGED
)
63 return l
.author
== r
.author
&& l
.changetime
== r
.changetime
;
67 bool operator!=(Change
const & l
, Change
const & r
)
73 bool operator==(Changes::Range
const & r1
, Changes::Range
const & r2
)
75 return r1
.start
== r2
.start
&& r1
.end
== r2
.end
;
79 bool operator!=(Changes::Range
const & r1
, Changes::Range
const & r2
)
85 bool Changes::Range::intersects(Range
const & r
) const
87 return r
.start
< end
&& r
.end
> start
; // end itself is not in the range!
91 void Changes::set(Change
const & change
, pos_type
const pos
)
93 set(change
, pos
, pos
+ 1);
97 void Changes::set(Change
const & change
, pos_type
const start
, pos_type
const end
)
99 if (change
.type
!= Change::UNCHANGED
) {
100 LYXERR(Debug::CHANGES
, "setting change (type: " << change
.type
101 << ", author: " << change
.author
102 << ", time: " << long(change
.changetime
)
103 << ") in range (" << start
<< ", " << end
<< ")");
106 Range
const newRange(start
, end
);
108 ChangeTable::iterator it
= table_
.begin();
110 for (; it
!= table_
.end(); ) {
111 // current change starts like or follows new change
112 if (it
->range
.start
>= start
) {
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
;
121 LYXERR(Debug::CHANGES
, " cutting tail of type " << it
->change
.type
122 << " resulting in range (" << it
->range
.start
<< ", "
123 << it
->range
.end
<< ")");
127 LYXERR(Debug::CHANGES
, " inserting tail in range ("
128 << end
<< ", " << oldEnd
<< ")");
129 it
= table_
.insert(it
, ChangeRange((it
-1)->change
, Range(end
, oldEnd
)));
137 if (change
.type
!= Change::UNCHANGED
) {
138 LYXERR(Debug::CHANGES
, " inserting change");
139 it
= table_
.insert(it
, ChangeRange(change
, Range(start
, end
)));
143 for (; it
!= table_
.end(); ) {
144 // new change 'contains' existing change
145 if (newRange
.contains(it
->range
)) {
146 LYXERR(Debug::CHANGES
, " removing subrange ("
147 << it
->range
.start
<< ", " << it
->range
.end
<< ")");
148 it
= table_
.erase(it
);
152 // new change precedes existing change
153 if (it
->range
.start
>= end
)
156 // new change intersects with existing change
157 it
->range
.start
= end
;
158 LYXERR(Debug::CHANGES
, " cutting head of type "
159 << it
->change
.type
<< " resulting in range ("
160 << end
<< ", " << it
->range
.end
<< ")");
161 break; // no need for another iteration
168 void Changes::erase(pos_type
const pos
)
170 LYXERR(Debug::CHANGES
, "Erasing change at position " << pos
);
172 ChangeTable::iterator it
= table_
.begin();
173 ChangeTable::iterator end
= table_
.end();
175 for (; it
!= end
; ++it
) {
176 // range (pos,pos+x) becomes (pos,pos+x-1)
177 if (it
->range
.start
> pos
)
179 // range (pos-x,pos) stays (pos-x,pos)
180 if (it
->range
.end
> pos
)
188 void Changes::insert(Change
const & change
, lyx::pos_type pos
)
190 if (change
.type
!= Change::UNCHANGED
) {
191 LYXERR(Debug::CHANGES
, "Inserting change of type " << change
.type
192 << " at position " << pos
);
195 ChangeTable::iterator it
= table_
.begin();
196 ChangeTable::iterator end
= table_
.end();
198 for (; it
!= end
; ++it
) {
199 // range (pos,pos+x) becomes (pos+1,pos+x+1)
200 if (it
->range
.start
>= pos
)
203 // range (pos-x,pos) stays as it is
204 if (it
->range
.end
> pos
)
208 set(change
, pos
, pos
+ 1); // set will call merge
212 Change
const & Changes::lookup(pos_type
const pos
) const
214 static Change
const noChange
= Change(Change::UNCHANGED
);
216 ChangeTable::const_iterator it
= table_
.begin();
217 ChangeTable::const_iterator
const end
= table_
.end();
219 for (; it
!= end
; ++it
) {
220 if (it
->range
.contains(pos
))
228 bool Changes::isChanged(pos_type
const start
, pos_type
const end
) const
230 ChangeTable::const_iterator it
= table_
.begin();
231 ChangeTable::const_iterator
const itend
= table_
.end();
233 for (; it
!= itend
; ++it
) {
234 if (it
->range
.intersects(Range(start
, end
))) {
235 LYXERR(Debug::CHANGES
, "found intersection of range ("
236 << start
<< ", " << end
<< ") with ("
237 << it
->range
.start
<< ", " << it
->range
.end
238 << ") of type " << it
->change
.type
);
246 void Changes::merge()
248 ChangeTable::iterator it
= table_
.begin();
250 while (it
!= table_
.end()) {
251 LYXERR(Debug::CHANGES
, "found change of type " << it
->change
.type
252 << " and range (" << it
->range
.start
<< ", " << it
->range
.end
255 if (it
->range
.start
== it
->range
.end
) {
256 LYXERR(Debug::CHANGES
, "removing empty range for pos "
265 if (it
+ 1 == table_
.end())
268 if (it
->change
.isSimilarTo((it
+ 1)->change
)
269 && it
->range
.end
== (it
+ 1)->range
.start
) {
270 LYXERR(Debug::CHANGES
, "merging ranges (" << it
->range
.start
<< ", "
271 << it
->range
.end
<< ") and (" << (it
+ 1)->range
.start
<< ", "
272 << (it
+ 1)->range
.end
<< ")");
274 (it
+ 1)->range
.start
= it
->range
.start
;
275 (it
+ 1)->change
.changetime
= max(it
->change
.changetime
,
276 (it
+ 1)->change
.changetime
);
288 int Changes::latexMarkChange(odocstream
& os
, BufferParams
const & bparams
,
289 Change
const & oldChange
, Change
const & change
)
291 if (!bparams
.outputChanges
|| oldChange
== change
)
296 if (oldChange
.type
!= Change::UNCHANGED
) {
297 os
<< '}'; // close \lyxadded or \lyxdeleted
302 chgTime
+= ctime(&change
.changetime
);
303 chgTime
.erase(chgTime
.end() - 1); // remove trailing '\n'
305 if (change
.type
== Change::DELETED
) {
306 docstring str
= "\\lyxdeleted{" +
307 bparams
.authors().get(change
.author
).name() + "}{" +
310 column
+= str
.size();
311 } else if (change
.type
== Change::INSERTED
) {
312 docstring str
= "\\lyxadded{" +
313 bparams
.authors().get(change
.author
).name() + "}{" +
316 column
+= str
.size();
323 void Changes::lyxMarkChange(ostream
& os
, int & column
,
324 Change
const & old
, Change
const & change
)
331 switch (change
.type
) {
332 case Change::UNCHANGED
:
333 os
<< "\n\\change_unchanged\n";
336 case Change::DELETED
: {
337 os
<< "\n\\change_deleted " << change
.author
338 << " " << change
.changetime
<< "\n";
342 case Change::INSERTED
: {
343 os
<< "\n\\change_inserted " << change
.author
344 << " " << change
.changetime
<< "\n";
351 void Changes::checkAuthors(AuthorList
const & authorList
)
353 ChangeTable::const_iterator it
= table_
.begin();
354 ChangeTable::const_iterator endit
= table_
.end();
355 for ( ; it
!= endit
; ++it
)
356 if (it
->change
.type
!= Change::UNCHANGED
)
357 authorList
.get(it
->change
.author
).setUsed(true);