]> git.lyx.org Git - lyx.git/blob - src/Compare.cpp
a84477ad6bcf8c156c4d94f495f27354bdba9704
[lyx.git] / src / Compare.cpp
1 /**
2  * \file Compare.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Vincent van Ravesteijn
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "Compare.h"
14
15 #include "BufferParams.h"
16 #include "Changes.h"
17
18 #include "insets/InsetText.h"
19
20 #include "support/lassert.h"    
21
22 #include <boost/next_prior.hpp>
23
24 #include <cmath>
25
26 using namespace std;
27 using namespace lyx::support;
28
29
30 namespace lyx {
31
32
33 enum Direction {
34         Forward = 0,
35         Backward
36 };
37
38
39 static void step(DocIterator & dit, Direction direction)
40 {
41         if (direction == Forward)
42                 dit.top().forwardPos();
43         else
44                 dit.top().backwardPos();
45 }
46
47
48 static void step(DocIterator & dit, DocIterator const & end, Direction direction)
49 {
50         if (dit != end)
51                 step(dit, direction);
52 }
53
54
55 /**
56  * A pair of two DocIterators that form a range.
57  */
58 class DocRange {
59 public:
60         DocRange(DocIterator from_, DocIterator to_)
61                 : from(from_), to(to_)
62         {}
63
64         DocRange(Buffer const * buf)
65         {
66                 from = doc_iterator_begin(buf);
67                 to = doc_iterator_end(buf);
68                 to.backwardPos();
69         }
70
71         ///
72         Text * text() const { return from.text(); }
73         ///
74         bool empty() const { return to <= from; }
75         ///
76         size_t length() const;
77
78         /// The begin of the range
79         DocIterator from;
80         /// The end of the range
81         DocIterator to;
82 };
83
84
85 size_t DocRange::length() const
86 {
87         pit_type startpit = from.pit();
88         pit_type endpit = to.pit();
89         ParagraphList const & ps_ = from.text()->paragraphs();
90
91         ParagraphList pars(boost::next(ps_.begin(), startpit),
92                                 boost::next(ps_.begin(), endpit + 1));
93
94         // Remove the end of the last paragraph; afterwards, remove the
95         // beginning of the first paragraph.
96         Paragraph & back = pars.back();
97         back.eraseChars(to.pos(), back.size(), false);
98         Paragraph & front = pars.front();
99         front.eraseChars(0, from.pos(), false);
100
101         ParagraphList::const_iterator pit = pars.begin();
102         ParagraphList::const_iterator end_it = pars.end();
103
104         size_t length = 0;
105         for (; pit != end_it; ++pit)
106                 length += pit->size() + 1;
107
108         // The last paragraph has no paragraph-end
109         --length;
110         return length;  
111 }
112
113
114 class DocPair {
115 public:
116         DocPair() {}
117
118         DocPair(DocIterator o_, DocIterator n_)
119                 : o(o_), n(n_)
120         {}
121
122         bool operator!=(DocPair const & rhs) {
123                 // this might not be intuitive but correct for our purpose
124                 return o != rhs.o && n != rhs.n;
125         }
126         
127
128         DocPair & operator++()
129         {
130                 step(o, Forward);
131                 step(n, Forward);
132                 return *this;
133         }
134
135         DocPair & operator--()
136         {
137                 step(o, Backward);
138                 step(n, Backward);
139                 return *this;
140         }
141         ///
142         DocIterator o;
143         ///
144         DocIterator n;
145 };
146         
147 /**
148  * A pair of two DocRanges.
149  */
150 class DocRangePair {
151 public:
152         DocRangePair(DocRange o_, DocRange n_)
153                 : o(o_), n(n_)
154         {}
155         
156         DocRangePair(DocPair from, DocPair to)
157                 : o(from.o, to.o), n(from.n, to.n)
158         {}
159
160         DocRangePair(Buffer const * o_buf, Buffer const * n_buf)
161                 : o(o_buf), n(n_buf)
162         {}
163
164         /// Returns the from pair
165         DocPair from() const { return DocPair(o.from, n.from); }
166
167         /// Returns the to pair
168         DocPair to() const { return DocPair(o.to, n.to); }
169
170         DocRange o;
171         DocRange n;
172 };
173
174
175 static DocRangePair stepIntoInset(DocPair const & inset_location)
176 {
177         DocRangePair rp(inset_location, inset_location);
178         rp.o.from.forwardPos();
179         rp.n.from.forwardPos();
180         step(rp.o.to, Forward);
181         step(rp.n.to, Forward);
182         rp.o.to.backwardPos();
183         rp.n.to.backwardPos();
184         return rp;
185 }
186
187
188 /**
189  *  This class is designed to hold a vector that has both positive as
190  *  negative indices. It is internally represented as two vectors, one
191  *  for non-zero indices and one for negative indices. In this way, the
192  *  vector can grow in both directions.
193  *    If an index is not available in the vector, the default value is
194  *  returned. If an object is put in the vector beyond its size, the
195  *  empty spots in between are also filled with the default value.
196  */
197 template<class T>
198 class compl_vector {
199 public:
200         compl_vector() {}
201
202         void reset(T const & def)
203         {
204                 default_ = def;
205                 Vp_.clear();
206                 Vn_.clear();
207         }
208
209         /// Gets the value at index. If it is not in the vector
210         /// the default value is inserted and returned.
211         T & operator[](int index) {
212                 vector<T> & V = index >= 0 ? Vp_ : Vn_;
213                 unsigned int const ii = index >= 0 ? index : -index - 1;
214                 while (ii >= V.size())
215                         V.push_back(default_);
216                 return V[ii];
217         }
218
219 private:
220         /// The vector for positive indices
221         vector<T> Vp_;
222         /// The vector for negative indices
223         vector<T> Vn_;
224         /// The default value that is inserted in the vector
225         /// if more space is needed
226         T default_;
227 };
228
229
230 /**
231  * The implementation of the algorithm that does the comparison
232  * between two documents.
233  */
234 class Compare::Impl {
235 public:
236         ///
237         Impl(Compare const & compare) 
238                 : abort_(false), compare_(compare)
239         {}
240
241         ///
242         ~Impl() {}
243
244         // Algorithm to find the shortest edit string. This algorithm 
245         // only needs a linear amount of memory (linear with the sum
246         // of the number of characters in the two paragraph-lists).
247         bool diff(Buffer const * new_buf, Buffer const * old_buf,
248                 Buffer const * dest_buf);
249
250         /// Set to true to cancel the algorithm
251         bool abort_;
252
253 private:
254         /// Finds the middle snake and returns the length of the
255         /// shortest edit script.
256         int find_middle_snake(DocRangePair const & rp, DocPair & middle_snake);
257
258         enum SnakeResult {
259                 NoSnake,
260                 SingleSnake,
261                 NormalSnake
262         };
263
264         /// Retrieve the middle snake when there is overlap between
265         /// the forward and backward path.
266         SnakeResult retrieve_middle_snake(int k, int D, Direction direction,
267                 DocPair & middle_snake);
268         
269         /// Find the the furthest reaching D-path (number of horizontal
270         /// and vertical steps; differences between the old and new
271         /// document) in the k-diagonal (vertical minus horizontal steps).
272         void furthest_Dpath_kdiagonal(int D, int k,
273                 DocRangePair const & rp, Direction direction);
274
275         /// Is there overlap between the forward and backward path
276         bool overlap(int k, int D);
277         
278         /// This function is called recursively by a divide and conquer
279         /// algorithm. Each time, the string is divided into two split
280         /// around the middle snake.
281         void diff_i(DocRangePair const & rp);
282
283         /// Processes the splitted chunks. It either adds them as deleted,
284         /// as added, or call diff_i for further processing.
285         void diff_part(DocRangePair const & rp);
286
287         /// Runs the algorithm for the inset located at /c it and /c it_n 
288         /// and adds the result to /c pars.
289         void diff_inset(Inset * inset, DocPair const & p);
290
291         /// Adds the snake to the destination buffer. The algorithm will
292         /// recursively be applied to any InsetTexts that are within the snake.
293         void process_snake(DocRangePair const & rp);
294
295         /// Writes the range to the destination buffer
296         void writeToDestBuffer(DocRange const & range,
297                 Change::Type type = Change::UNCHANGED);
298         
299         /// Writes the paragraph list to the destination buffer
300         void writeToDestBuffer(ParagraphList const & copy_pars) const;
301
302         /// The length of the old chunk currently processed
303         int N_;
304         /// The length of the new chunk currently processed
305         int M_;
306         /// The offset diagonal of the reverse path of the
307         /// currently processed chunk
308         int offset_reverse_diagonal_;
309         /// Is the offset odd or even ?
310         bool odd_offset_;
311
312         /// The thread object, used to emit signals to the GUI
313         Compare const & compare_;
314
315         /// The buffer containing text that will be marked as old
316         Buffer const * old_buf_;
317         /// The buffer containing text that will be marked as new
318         Buffer const * new_buf_;
319         /// The buffer containing text that will be marked as new
320         Buffer const * dest_buf_;
321
322         /// The paragraph list of the destination buffer
323         ParagraphList * dest_pars_;
324
325         /// The level of recursion
326         int recursion_level_;
327
328         /// The number of nested insets at this level
329         int nested_inset_level_;
330
331         /// The position/snake in the old/new document
332         /// of the forward/reverse search
333         compl_vector<DocIterator> ofp;
334         compl_vector<DocIterator> nfp;
335         compl_vector<DocIterator> ofs;
336         compl_vector<DocIterator> nfs;
337         compl_vector<DocIterator> orp;
338         compl_vector<DocIterator> nrp;
339         compl_vector<DocIterator> ors;
340         compl_vector<DocIterator> nrs;
341 };
342
343 /////////////////////////////////////////////////////////////////////
344 //
345 // Compare
346 //
347 /////////////////////////////////////////////////////////////////////
348
349 Compare::Compare(Buffer const * new_buf, Buffer const * old_buf,
350         Buffer * const dest_buf, CompareOptions const & options)
351         : new_buffer(new_buf), old_buffer(old_buf), dest_buffer(dest_buf),
352           options_(options), pimpl_(new Impl(*this))
353 {
354 }
355
356
357 void Compare::run()
358 {
359         if (!dest_buffer || !new_buffer || !old_buffer)
360                 return;
361
362         // Copy the buffer params to the new buffer
363         dest_buffer->params() = options_.settings_from_new
364                 ? new_buffer->params() : old_buffer->params();
365         
366         // do the real work
367         if (!doCompare())
368                 return;
369         
370         finished(pimpl_->abort_);
371         return;
372 }
373
374
375 int Compare::doCompare()
376 {
377         return pimpl_->diff(new_buffer, old_buffer, dest_buffer);
378 }
379
380
381 void Compare::abort()
382 {
383         pimpl_->abort_ = true;
384         condition_.wakeOne();
385         wait();
386         pimpl_->abort_ = false;
387 }
388
389
390 static void get_paragraph_list(DocRange const & range,
391         ParagraphList & pars)
392 {
393         // Clone the paragraphs within the selection.
394         pit_type startpit = range.from.pit();
395         pit_type endpit = range.to.pit();
396         ParagraphList const & ps_ = range.text()->paragraphs();
397         ParagraphList tmp_pars(boost::next(ps_.begin(), startpit),
398                 boost::next(ps_.begin(), endpit + 1));
399
400         // Remove the end of the last paragraph; afterwards, remove the
401         // beginning of the first paragraph. Keep this order - there may only
402         // be one paragraph!
403         Paragraph & back = tmp_pars.back();
404         back.eraseChars(range.to.pos(), back.size(), false);
405         Paragraph & front = tmp_pars.front();
406         front.eraseChars(0, range.from.pos(), false);
407
408         pars.insert(pars.begin(), tmp_pars.begin(), tmp_pars.end());
409 }
410
411
412 static bool equal(Inset const * i_o, Inset const * i_n)
413 {
414         if (!i_o || !i_n)
415                 return false;
416
417         // Different types of insets
418         if (i_o->lyxCode() != i_n->lyxCode())
419                 return false;
420
421         // Editable insets are assumed to be the same as they are of the 
422         // same type. If we later on decide that we insert them in the
423         // document as being unchanged, we will run the algorithm on the
424         // contents of the two insets.
425         // FIXME: This fails if the parameters of the insets differ.
426         // FIXME: We do not recurse into InsetTabulars.
427         // FIXME: We need methods inset->equivalent(inset).
428         if (i_o->editable() && !i_o->asInsetMath()
429                   && i_o->asInsetText())
430                 return true;
431
432         ostringstream o_os;
433         ostringstream n_os;
434         i_o->write(o_os);
435         i_n->write(n_os);
436         return o_os.str() == n_os.str();
437 }
438
439
440 static bool equal(DocIterator & o, DocIterator & n) {
441         Paragraph const & old_par = o.text()->getPar(o.pit());
442         Paragraph const & new_par = n.text()->getPar(n.pit());
443
444         char_type const c_o = old_par.getChar(o.pos());
445         char_type const c_n = new_par.getChar(n.pos());
446         if (c_o != c_n)
447                 return false;
448
449         if (old_par.isInset(o.pos())) {
450                 Inset const * i_o = old_par.getInset(o.pos());
451                 Inset const * i_n = new_par.getInset(n.pos());
452
453                 if (i_o && i_n)
454                         return equal(i_o, i_n);
455         }       
456
457         Font fo = old_par.getFontSettings(o.buffer()->params(), o.pos());
458         Font fn = new_par.getFontSettings(n.buffer()->params(), n.pos());
459         return fo == fn;
460 }
461
462
463 /// Traverses a snake in a certain direction. p points to a 
464 /// position in the old and new file and they are synchronously
465 /// moved along the snake. The function returns true if a snake
466 /// was found.
467 static bool traverse_snake(DocPair & p, DocRangePair const & range,
468         Direction direction)
469 {
470         bool ret = false;
471         DocPair const & p_end = 
472                 direction == Forward ? range.to() : range.from();
473
474         while (p != p_end) {
475                 if (direction == Backward)
476                         --p;
477                 if (!equal(p.o, p.n)) {
478                         if (direction == Backward)
479                                 ++p;
480                         return ret;
481                 }
482                 if (direction == Forward)
483                         ++p;
484                 ret = true;
485         }
486         return ret;
487 }
488
489
490 /////////////////////////////////////////////////////////////////////
491 //
492 // Compare::Impl
493 //
494 /////////////////////////////////////////////////////////////////////
495
496
497 void Compare::Impl::furthest_Dpath_kdiagonal(int D, int k,
498          DocRangePair const & rp, Direction direction)
499 {
500         compl_vector<DocIterator> & op = direction == Forward ? ofp : orp;
501         compl_vector<DocIterator> & np = direction == Forward ? nfp : nrp;
502         compl_vector<DocIterator> & os = direction == Forward ? ofs : ors;
503         compl_vector<DocIterator> & ns = direction == Forward ? nfs : nrs;
504
505         // A vertical step means stepping one character in the new document.
506         bool vertical_step = k == -D;
507         if (!vertical_step && k != D) {
508                 vertical_step = direction == Forward
509                         ? op[k - 1] < op[k + 1] : op[k - 1] > op[k + 1];
510         }
511
512         // Where do we take the step from ?
513         int const kk = vertical_step ? k + 1 : k - 1;
514         DocPair p(op[kk], np[kk]);
515
516         // If D==0 we simulate a vertical step from (0,-1) by doing nothing.
517         if (D != 0) {
518                 // Take a step
519                 if (vertical_step && direction == Forward)
520                         step(p.n, rp.n.to, direction);
521                 else if (vertical_step && direction == Backward)
522                         step(p.n, rp.n.from, direction);
523                 else if (!vertical_step && direction == Forward)
524                         step(p.o, rp.o.to, direction);
525                 else if (!vertical_step && direction == Backward)
526                         step(p.o, rp.o.from, direction);
527         }       
528         
529         // Traverse snake
530         if (traverse_snake(p, rp, direction)) {
531                 // Record last snake
532                 os[k] = p.o;
533                 ns[k] = p.n;
534         } else {
535                 // Copy last snake from the previous step
536                 os[k] = os[kk];
537                 ns[k] = ns[kk];
538         }
539
540         //Record new position
541         op[k] = p.o;
542         np[k] = p.n;
543 }
544
545
546 bool Compare::Impl::overlap(int k, int D)
547 {
548         // To generalize for the forward and reverse checks
549         int kk = offset_reverse_diagonal_ - k;
550
551         // Can we have overlap ?
552         if (kk <= D && kk >= -D) {
553                 // Do we have overlap ?
554                 if (odd_offset_)
555                         return ofp[k] >= orp[kk] && nfp[k] >= nrp[kk];
556                 else
557                         return ofp[kk] >= orp[k] && nfp[kk] >= nrp[k];
558         }
559         return false;
560 }
561
562
563 Compare::Impl::SnakeResult Compare::Impl::retrieve_middle_snake(
564         int k, int D, Direction direction, DocPair & middle_snake)
565 {
566         compl_vector<DocIterator> & os = direction == Forward ? ofs : ors;
567         compl_vector<DocIterator> & ns = direction == Forward ? nfs : nrs;
568         compl_vector<DocIterator> & os_r = direction == Forward ? ors : ofs;
569         compl_vector<DocIterator> & ns_r = direction == Forward ? nrs : nfs;
570
571         // The diagonal while doing the backward search
572         int kk = -k + offset_reverse_diagonal_;
573
574         // Did we find a snake ?
575         if (os[k].empty() && os_r[kk].empty()) {
576                 // No, there is no snake at all, in which case
577                 // the length of the shortest edit script is M+N.
578                 LASSERT(2 * D - odd_offset_ == M_ + N_, /**/);
579                 return NoSnake;
580         } 
581         
582         if (os[k].empty()) {
583                 // Yes, but there is only 1 snake and we found it in the
584                 // reverse path.
585                 middle_snake.o = os_r[kk];
586                 middle_snake.n = ns_r[kk];
587                 return SingleSnake;
588         }
589
590         middle_snake.o = os[k];
591         middle_snake.n = ns[k];
592         return NormalSnake;
593 }
594
595
596 int Compare::Impl::find_middle_snake(DocRangePair const & rp,
597         DocPair & middle_snake)
598 {
599         // The lengths of the old and new chunks.
600         N_ = rp.o.length();
601         M_ = rp.n.length();
602
603         // Forward paths are centered around the 0-diagonal; reverse paths
604         // are centered around the diagonal N - M. (Delta in the article)
605         offset_reverse_diagonal_ = N_ - M_;
606
607         // If the offset is odd, only check for overlap while extending forward
608     // paths, otherwise only check while extending reverse paths.
609         odd_offset_ = (offset_reverse_diagonal_ % 2 != 0);
610
611         ofp.reset(rp.o.from);
612         nfp.reset(rp.n.from);
613         ofs.reset(DocIterator());
614         nfs.reset(DocIterator());
615         orp.reset(rp.o.to);
616         nrp.reset(rp.n.to);
617         ors.reset(DocIterator());
618         nrs.reset(DocIterator());
619
620         // D is the number of horizontal and vertical steps, i.e.
621         // different characters in the old and new chunk.
622         int const D_max = ceil(((double)M_ + N_)/2);
623         for (int D = 0; D <= D_max; ++D) {
624
625                 // Forward and reverse paths
626                 for (int f = 0; f < 2; ++f) {
627                         Direction direction = f == 0 ? Forward : Backward;
628
629                         // Diagonals between -D and D can be reached by a D-path
630                         for (int k = -D; k <= D; k += 2) {                      
631                                 // Find the furthest reaching D-path on this diagonal
632                                 furthest_Dpath_kdiagonal(D, k, rp, direction);
633
634                                 // Only check for overlap for forward paths if the offset is odd
635                                 // and only for reverse paths if the offset is even.
636                                 if (odd_offset_ == (direction == Forward)) {
637
638                                         // Do the forward and backward paths overlap ?
639                                         if (overlap(k, D - odd_offset_)) {
640                                                 retrieve_middle_snake(k, D, direction, middle_snake);
641                                                 return 2 * D - odd_offset_;
642                                         }
643                                 }
644                         }
645                 }
646         }
647         // This should never be reached
648         return -2;
649 }
650
651
652 bool Compare::Impl::diff(Buffer const * new_buf, Buffer const * old_buf,
653         Buffer const * dest_buf)
654 {
655         if (!new_buf || !old_buf || !dest_buf)
656                 return false;
657
658         old_buf_ = old_buf;
659         new_buf_ = new_buf;
660         dest_buf_ = dest_buf;
661         dest_pars_ = &dest_buf->inset().asInsetText()->paragraphs();
662         dest_pars_->clear();
663
664         recursion_level_ = 0;
665         nested_inset_level_ = 0;
666
667         DocRangePair rp(old_buf_, new_buf_);
668
669         DocPair from = rp.from();
670         traverse_snake(from, rp, Forward);
671         DocRangePair const snake(rp.from(), from);
672         process_snake(snake);
673         
674         // Start the recursive algorithm
675         diff_i(rp);
676
677         for (pit_type p = 0; p < (pit_type)dest_pars_->size(); ++p) {
678                 (*dest_pars_)[p].setBuffer(const_cast<Buffer &>(*dest_buf));
679                 (*dest_pars_)[p].setInsetOwner(&dest_buf_->inset());
680         }
681
682         return true;
683 }
684
685
686 void Compare::Impl::diff_i(DocRangePair const & rp)
687 {
688         // The middle snake
689         DocPair middle_snake;
690
691         // Divides the problem into two smaller problems, split around
692         // the snake in the middle.
693         int const L_ses = find_middle_snake(rp, middle_snake);
694
695         // Set maximum of progress bar
696         if (++recursion_level_ == 1)
697                 compare_.progressMax(L_ses);
698
699         // There are now three possibilities: the strings were the same,
700         // the strings were completely different, or we found a middle
701         // snake and we can split the string into two parts to process.
702         if (L_ses == 0)
703                 // Two the same strings (this must be a very rare case, because
704                 // usually this will be part of a snake adjacent to these strings).
705                 writeToDestBuffer(rp.o);
706
707         else if (middle_snake.o.empty()) {
708                 // Two totally different strings
709                 writeToDestBuffer(rp.o, Change::DELETED);
710                 writeToDestBuffer(rp.n, Change::INSERTED);
711
712         } else {
713                 // Retrieve the complete snake
714                 DocPair first_part_end = middle_snake;
715                 traverse_snake(first_part_end, rp, Backward);
716                 DocRangePair first_part(rp.from(), first_part_end);
717                         
718                 DocPair second_part_begin = middle_snake;
719                 traverse_snake(second_part_begin, rp, Forward);
720                 DocRangePair second_part(second_part_begin, rp.to());
721                                 
722                 // Split the string in three parts:
723                 // 1. in front of the snake
724                 diff_part(first_part);
725
726                 // 2. the snake itself, and
727                 DocRangePair const snake(first_part.to(), second_part.from());
728                 process_snake(snake);
729
730                 // 3. behind the snake.
731                 diff_part(second_part);
732         }
733         --recursion_level_;
734 }
735
736
737 void Compare::Impl::diff_part(DocRangePair const & rp)
738 {
739         // Is there a finite length string in both buffers, if not there
740         // is an empty string and we write the other one to the buffer.
741         if (!rp.o.empty() && !rp.n.empty())
742                 diff_i(rp);
743         
744         else if (!rp.o.empty())
745                 writeToDestBuffer(rp.o, Change::DELETED);
746
747         else if (!rp.n.empty())
748                 writeToDestBuffer(rp.n, Change::INSERTED);
749 }
750
751
752 void Compare::Impl::diff_inset(Inset * inset, DocPair const & p)
753 {
754         // Find the dociterators for the beginning and the
755         // end of the inset, for the old and new document.
756         DocRangePair const rp = stepIntoInset(p);
757
758         // Recurse into the inset. Temporarily replace the dest_pars
759         // paragraph list by the paragraph list of the nested inset.
760         ParagraphList * backup_dest_pars = dest_pars_;
761         dest_pars_ = &inset->asInsetText()->text().paragraphs();
762         dest_pars_->clear();
763         
764         ++nested_inset_level_;
765         diff_i(rp);
766         --nested_inset_level_;
767
768         dest_pars_ = backup_dest_pars;
769 }
770
771
772 void Compare::Impl::process_snake(DocRangePair const & rp)
773 {
774         ParagraphList pars;
775         get_paragraph_list(rp.o, pars);
776
777         // Find insets in this paragaph list
778         DocPair it = rp.from();
779         for (; it.o < rp.o.to; ++it) {
780                 Inset * inset = it.o.text()->getPar(it.o.pit()).getInset(it.o.pos());
781                 if (inset && inset->editable() && inset->asInsetText()) {
782                         // Find the inset in the paragraph list that will be pasted into
783                         // the final document. The contents of the inset will be replaced
784                         // by the output of the algorithm below.
785                         pit_type const pit = it.o.pit() - rp.o.from.pit();
786                         pos_type const pos = pit ? it.o.pos() : it.o.pos() - rp.o.from.pos();
787                         inset = pars[pit].getInset(pos);
788                         LASSERT(inset, /**/);
789                         diff_inset(inset, it);
790                 }
791         }
792         writeToDestBuffer(pars);
793 }
794
795
796 void Compare::Impl::writeToDestBuffer(DocRange const & range,
797         Change::Type type)
798 {
799         ParagraphList pars;
800         get_paragraph_list(range, pars);
801
802         pos_type size = 0;
803
804         // Set the change
805         ParagraphList::iterator it = pars.begin();
806         for (; it != pars.end(); ++it) {
807                 it->setChange(Change(type));
808                 size += it->size();
809         }
810
811         writeToDestBuffer(pars);
812
813         if (nested_inset_level_ == 0)
814                 compare_.progress(size);
815 }
816
817
818 void Compare::Impl::writeToDestBuffer(ParagraphList const & pars) const
819 {
820         pit_type const pit = dest_pars_->size() - 1;
821         dest_pars_->insert(dest_pars_->end(), pars.begin(), pars.end());
822         if (pit >= 0)
823                 mergeParagraph(dest_buf_->params(), *dest_pars_, pit);
824 }
825
826
827 #include "moc_Compare.cpp"
828
829 } // namespace lyx