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.
19 #include "BufferParams.h"
20 #include "LaTeXFeatures.h"
21 #include "Paragraph.h"
22 #include "TocBackend.h"
24 #include "support/debug.h"
25 #include "support/gettext.h"
26 #include "support/lassert.h"
35 * Class Change has a changetime field that specifies the exact time at which
36 * a specific change was made. The change time is used as a guidance for the
37 * user while editing his document. Presently, it is not considered for LaTeX
39 * When merging two adjacent changes, the changetime is not considered,
40 * only the equality of the change type and author is checked (in method
41 * isSimilarTo(...)). If two changes are in fact merged (in method merge()),
42 * the later change time is preserved.
45 bool Change::isSimilarTo(Change
const & change
) const
47 if (type
!= change
.type
)
50 if (type
== Change::UNCHANGED
)
53 return author
== change
.author
;
57 Color
Change::color() const
59 Color color
= Color_none
;
62 color
= Color_changedtextauthor1
;
65 color
= Color_changedtextauthor2
;
68 color
= Color_changedtextauthor3
;
71 color
= Color_changedtextauthor4
;
74 color
= Color_changedtextauthor5
;
79 color
.mergeColor
= Color_deletedtextmodifier
;
85 bool operator==(Change
const & l
, Change
const & r
)
90 // two changes of type UNCHANGED are always equal
91 if (l
.type
== Change::UNCHANGED
)
94 return l
.author
== r
.author
&& l
.changetime
== r
.changetime
;
98 bool operator!=(Change
const & l
, Change
const & r
)
104 bool operator==(Changes::Range
const & r1
, Changes::Range
const & r2
)
106 return r1
.start
== r2
.start
&& r1
.end
== r2
.end
;
110 bool operator!=(Changes::Range
const & r1
, Changes::Range
const & r2
)
116 bool Changes::Range::intersects(Range
const & r
) const
118 return r
.start
< end
&& r
.end
> start
; // end itself is not in the range!
122 void Changes::set(Change
const & change
, pos_type
const pos
)
124 set(change
, pos
, pos
+ 1);
128 void Changes::set(Change
const & change
, pos_type
const start
, pos_type
const end
)
130 if (change
.type
!= Change::UNCHANGED
) {
131 LYXERR(Debug::CHANGES
, "setting change (type: " << change
.type
132 << ", author: " << change
.author
133 << ", time: " << long(change
.changetime
)
134 << ") in range (" << start
<< ", " << end
<< ")");
137 Range
const newRange(start
, end
);
139 ChangeTable::iterator it
= table_
.begin();
141 for (; it
!= table_
.end(); ) {
142 // current change starts like or follows new change
143 if (it
->range
.start
>= start
) {
147 // new change intersects with existing change
148 if (it
->range
.end
> start
) {
149 pos_type oldEnd
= it
->range
.end
;
150 it
->range
.end
= start
;
152 LYXERR(Debug::CHANGES
, " cutting tail of type " << it
->change
.type
153 << " resulting in range (" << it
->range
.start
<< ", "
154 << it
->range
.end
<< ")");
158 LYXERR(Debug::CHANGES
, " inserting tail in range ("
159 << end
<< ", " << oldEnd
<< ")");
160 it
= table_
.insert(it
, ChangeRange((it
-1)->change
, Range(end
, oldEnd
)));
168 if (change
.type
!= Change::UNCHANGED
) {
169 LYXERR(Debug::CHANGES
, " inserting change");
170 it
= table_
.insert(it
, ChangeRange(change
, Range(start
, end
)));
174 for (; it
!= table_
.end(); ) {
175 // new change 'contains' existing change
176 if (newRange
.contains(it
->range
)) {
177 LYXERR(Debug::CHANGES
, " removing subrange ("
178 << it
->range
.start
<< ", " << it
->range
.end
<< ")");
179 it
= table_
.erase(it
);
183 // new change precedes existing change
184 if (it
->range
.start
>= end
)
187 // new change intersects with existing change
188 it
->range
.start
= end
;
189 LYXERR(Debug::CHANGES
, " cutting head of type "
190 << it
->change
.type
<< " resulting in range ("
191 << end
<< ", " << it
->range
.end
<< ")");
192 break; // no need for another iteration
199 void Changes::erase(pos_type
const pos
)
201 LYXERR(Debug::CHANGES
, "Erasing change at position " << pos
);
203 ChangeTable::iterator it
= table_
.begin();
204 ChangeTable::iterator end
= table_
.end();
206 for (; it
!= end
; ++it
) {
207 // range (pos,pos+x) becomes (pos,pos+x-1)
208 if (it
->range
.start
> pos
)
210 // range (pos-x,pos) stays (pos-x,pos)
211 if (it
->range
.end
> pos
)
219 void Changes::insert(Change
const & change
, lyx::pos_type pos
)
221 if (change
.type
!= Change::UNCHANGED
) {
222 LYXERR(Debug::CHANGES
, "Inserting change of type " << change
.type
223 << " at position " << pos
);
226 ChangeTable::iterator it
= table_
.begin();
227 ChangeTable::iterator end
= table_
.end();
229 for (; it
!= end
; ++it
) {
230 // range (pos,pos+x) becomes (pos+1,pos+x+1)
231 if (it
->range
.start
>= pos
)
234 // range (pos-x,pos) stays as it is
235 if (it
->range
.end
> pos
)
239 set(change
, pos
, pos
+ 1); // set will call merge
243 Change
const & Changes::lookup(pos_type
const pos
) const
245 static Change
const noChange
= Change(Change::UNCHANGED
);
247 ChangeTable::const_iterator it
= table_
.begin();
248 ChangeTable::const_iterator
const end
= table_
.end();
250 for (; it
!= end
; ++it
) {
251 if (it
->range
.contains(pos
))
259 bool Changes::isDeleted(pos_type start
, pos_type end
) const
261 ChangeTable::const_iterator it
= table_
.begin();
262 ChangeTable::const_iterator
const itend
= table_
.end();
264 for (; it
!= itend
; ++it
) {
265 if (it
->range
.contains(Range(start
, end
))) {
266 LYXERR(Debug::CHANGES
, "range ("
267 << start
<< ", " << end
<< ") fully contains ("
268 << it
->range
.start
<< ", " << it
->range
.end
269 << ") of type " << it
->change
.type
);
270 return it
->change
.type
== Change::DELETED
;
277 bool Changes::isChanged(pos_type
const start
, pos_type
const end
) const
279 ChangeTable::const_iterator it
= table_
.begin();
280 ChangeTable::const_iterator
const itend
= table_
.end();
282 for (; it
!= itend
; ++it
) {
283 if (it
->range
.intersects(Range(start
, end
))) {
284 LYXERR(Debug::CHANGES
, "found intersection of range ("
285 << start
<< ", " << end
<< ") with ("
286 << it
->range
.start
<< ", " << it
->range
.end
287 << ") of type " << it
->change
.type
);
295 void Changes::merge()
297 ChangeTable::iterator it
= table_
.begin();
299 while (it
!= table_
.end()) {
300 LYXERR(Debug::CHANGES
, "found change of type " << it
->change
.type
301 << " and range (" << it
->range
.start
<< ", " << it
->range
.end
304 if (it
->range
.start
== it
->range
.end
) {
305 LYXERR(Debug::CHANGES
, "removing empty range for pos "
314 if (it
+ 1 == table_
.end())
317 if (it
->change
.isSimilarTo((it
+ 1)->change
)
318 && it
->range
.end
== (it
+ 1)->range
.start
) {
319 LYXERR(Debug::CHANGES
, "merging ranges (" << it
->range
.start
<< ", "
320 << it
->range
.end
<< ") and (" << (it
+ 1)->range
.start
<< ", "
321 << (it
+ 1)->range
.end
<< ")");
323 (it
+ 1)->range
.start
= it
->range
.start
;
324 (it
+ 1)->change
.changetime
= max(it
->change
.changetime
,
325 (it
+ 1)->change
.changetime
);
337 int Changes::latexMarkChange(odocstream
& os
, BufferParams
const & bparams
,
338 Change
const & oldChange
, Change
const & change
)
340 if (!bparams
.outputChanges
|| oldChange
== change
)
345 if (oldChange
.type
!= Change::UNCHANGED
) {
346 os
<< '}'; // close \lyxadded or \lyxdeleted
351 chgTime
+= ctime(&change
.changetime
);
352 chgTime
.erase(chgTime
.end() - 1); // remove trailing '\n'
354 if (change
.type
== Change::DELETED
) {
355 docstring str
= "\\lyxdeleted{" +
356 bparams
.authors().get(change
.author
).name() + "}{" +
359 column
+= str
.size();
360 } else if (change
.type
== Change::INSERTED
) {
361 docstring str
= "\\lyxadded{" +
362 bparams
.authors().get(change
.author
).name() + "}{" +
365 column
+= str
.size();
372 void Changes::lyxMarkChange(ostream
& os
, BufferParams
const & bparams
, int & column
,
373 Change
const & old
, Change
const & change
)
380 int const buffer_id
= bparams
.authors().get(change
.author
).buffer_id();
382 switch (change
.type
) {
383 case Change::UNCHANGED
:
384 os
<< "\n\\change_unchanged\n";
387 case Change::DELETED
: {
388 os
<< "\n\\change_deleted " << buffer_id
389 << " " << change
.changetime
<< "\n";
393 case Change::INSERTED
: {
394 os
<< "\n\\change_inserted " << buffer_id
395 << " " << change
.changetime
<< "\n";
402 void Changes::checkAuthors(AuthorList
const & authorList
)
404 ChangeTable::const_iterator it
= table_
.begin();
405 ChangeTable::const_iterator endit
= table_
.end();
406 for ( ; it
!= endit
; ++it
)
407 if (it
->change
.type
!= Change::UNCHANGED
)
408 authorList
.get(it
->change
.author
).setUsed(true);
412 void Changes::addToToc(DocIterator
const & cdit
, Buffer
const & buffer
) const
417 Toc
& change_list
= buffer
.tocBackend().toc("change");
418 AuthorList
const & author_list
= buffer
.params().authors();
419 DocIterator dit
= cdit
;
421 ChangeTable::const_iterator it
= table_
.begin();
422 ChangeTable::const_iterator
const itend
= table_
.end();
423 for (; it
!= itend
; ++it
) {
425 switch (it
->change
.type
) {
426 case Change::UNCHANGED
:
428 case Change::DELETED
:
429 // 0x2702 is a scissors symbol in the Dingbats unicode group.
430 str
.push_back(0x2702);
432 case Change::INSERTED
:
433 // 0x270d is the hand writting symbol in the Dingbats unicode group.
434 str
.push_back(0x270d);
437 dit
.pos() = it
->range
.start
;
438 Paragraph
const & par
= dit
.paragraph();
439 str
+= " " + par
.asString(it
->range
.start
, min(par
.size(), it
->range
.end
));
440 if (it
->range
.end
> par
.size())
441 // the end of paragraph symbol from the Punctuation group
442 str
.push_back(0x204B);
443 docstring
const & author
= author_list
.get(it
->change
.author
).name();
444 Toc::iterator it
= change_list
.item(0, author
);
445 if (it
== change_list
.end()) {
446 change_list
.push_back(TocItem(dit
, 0, author
));
447 change_list
.push_back(TocItem(dit
, 1, str
));
450 for (++it
; it
!= change_list
.end(); ++it
) {
451 if (it
->depth() == 0 && it
->str() != author
)
454 change_list
.insert(it
, TocItem(dit
, 1, str
));