]> git.lyx.org Git - lyx.git/blob - src/Compare.cpp
Cosmetics mainly: Rename math.h to cmath, reorder some includes, remove some includes...
[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
210         /// Gets the value at index. If it is not in the vector
211         /// the default value is returned.
212         T & get(int index) {
213                 if (-index <= int(Vn_.size()) && index < int(Vp_.size()))
214                         return index >= 0 ? Vp_[index] : Vn_[-index-1];
215                 else
216                         return default_;
217         }
218
219         /// Sets the value at index if it already
220         /// is in the vector. Otherwise it will be added to the
221         /// end padded with the default value.
222         void set(int index, T const & t) {
223                 if (index >= -int(Vn_.size()) && index < int(Vp_.size())) {
224                         if (index >= 0)
225                                 Vp_[index] = t;
226                         else 
227                                 Vn_[-index-1] = t;
228                 } else {
229                         while (index > int(Vp_.size()))
230                                 Vp_.push_back(default_);
231                         while (index < -int(Vn_.size()) - 1)
232                                 Vn_.push_back(default_);
233
234                         if (index >= 0)
235                                 Vp_.push_back(t);
236                         else
237                                 Vn_.push_back(t);
238                 }
239         }
240
241 private:
242         /// The vector for positive indices
243         vector<T> Vp_;
244         /// The vector for negative indices
245         vector<T> Vn_;
246         /// The default value that is inserted in the vector
247         /// if more space is needed
248         T default_;
249 };
250
251
252 /**
253  * The implementation of the algorithm that does the comparison
254  * between two documents.
255  */
256 class Compare::Impl {
257 public:
258         ///
259         Impl(Compare const & compare) 
260                 : abort_(false), compare_(compare)
261         {}
262
263         ///
264         ~Impl() {}
265
266         // Algorithm to find the shortest edit string. This algorithm 
267         // only needs a linear amount of memory (linear with the sum
268         // of the number of characters in the two paragraph-lists).
269         bool diff(Buffer const * new_buf, Buffer const * old_buf,
270                 Buffer const * dest_buf);
271
272         /// Set to true to cancel the algorithm
273         bool abort_;
274
275 private:
276         /// Finds the middle snake and returns the length of the
277         /// shortest edit script.
278         int find_middle_snake(DocRangePair const & rp, DocPair & middle_snake);
279
280         enum SnakeResult {
281                 NoSnake,
282                 SingleSnake,
283                 NormalSnake
284         };
285
286         /// Retrieve the middle snake when there is overlap between
287         /// the forward and backward path.
288         SnakeResult retrieve_middle_snake(int k, int D, Direction direction,
289                 DocPair & middle_snake);
290         
291         /// Find the the furthest reaching D-path (number of horizontal
292         /// and vertical steps; differences between the old and new
293         /// document) in the k-diagonal (vertical minus horizontal steps).
294         void furthest_Dpath_kdiagonal(int D, int k,
295                 DocRangePair const & rp, Direction direction);
296
297         /// Is there overlap between the forward and backward path
298         bool overlap(int k, int D);
299         
300         /// This function is called recursively by a divide and conquer
301         /// algorithm. Each time, the string is divided into two split
302         /// around the middle snake.
303         void diff_i(DocRangePair const & rp);
304
305         /// Processes the splitted chunks. It either adds them as deleted,
306         /// as added, or call diff_i for further processing.
307         void diff_part(DocRangePair const & rp);
308
309         /// Runs the algorithm for the inset located at /c it and /c it_n 
310         /// and adds the result to /c pars.
311         void diff_inset(Inset * inset, DocPair const & p);
312
313         /// Adds the snake to the destination buffer. The algorithm will
314         /// recursively be applied to any InsetTexts that are within the snake.
315         void process_snake(DocRangePair const & rp);
316
317         /// Writes the range to the destination buffer
318         void writeToDestBuffer(DocRange const & range,
319                 Change::Type type = Change::UNCHANGED);
320         
321         /// Writes the paragraph list to the destination buffer
322         void writeToDestBuffer(ParagraphList const & copy_pars) const;
323
324         /// The length of the old chunk currently processed
325         int N_;
326         /// The length of the new chunk currently processed
327         int M_;
328         /// The offset diagonal of the reverse path of the
329         /// currently processed chunk
330         int offset_reverse_diagonal_;
331         /// Is the offset odd or even ?
332         bool odd_offset_;
333
334         /// The thread object, used to emit signals to the GUI
335         Compare const & compare_;
336
337         /// The buffer containing text that will be marked as old
338         Buffer const * old_buf_;
339         /// The buffer containing text that will be marked as new
340         Buffer const * new_buf_;
341         /// The buffer containing text that will be marked as new
342         Buffer const * dest_buf_;
343
344         /// The paragraph list of the destination buffer
345         ParagraphList * dest_pars_;
346
347         /// The level of recursion
348         int recursion_level_;
349
350         /// The number of nested insets at this level
351         int nested_inset_level_;
352
353         /// The position/snake in the old/new document
354         /// of the forward/reverse search
355         compl_vector<DocIterator> ofp;
356         compl_vector<DocIterator> nfp;
357         compl_vector<DocIterator> ofs;
358         compl_vector<DocIterator> nfs;
359         compl_vector<DocIterator> orp;
360         compl_vector<DocIterator> nrp;
361         compl_vector<DocIterator> ors;
362         compl_vector<DocIterator> nrs;
363 };
364
365 /////////////////////////////////////////////////////////////////////
366 //
367 // Compare
368 //
369 /////////////////////////////////////////////////////////////////////
370
371 Compare::Compare(Buffer const * new_buf, Buffer const * old_buf,
372         Buffer * const dest_buf, CompareOptions const & options)
373         : new_buffer(new_buf), old_buffer(old_buf), dest_buffer(dest_buf),
374           options_(options), pimpl_(new Impl(*this))
375 {
376 }
377
378
379 void Compare::run()
380 {
381         if (!dest_buffer || !new_buffer || !old_buffer)
382                 return;
383
384         // Copy the buffer params to the new buffer
385         dest_buffer->params() = options_.settings_from_new
386                 ? new_buffer->params() : old_buffer->params();
387         
388         // do the real work
389         if (!doCompare())
390                 return;
391         
392         finished(pimpl_->abort_);
393         return;
394 }
395
396
397 int Compare::doCompare()
398 {
399         return pimpl_->diff(new_buffer, old_buffer, dest_buffer);
400 }
401
402
403 void Compare::abort()
404 {
405         pimpl_->abort_ = true;
406         condition_.wakeOne();
407         wait();
408         pimpl_->abort_ = false;
409 }
410
411
412 static void get_paragraph_list(DocRange const & range,
413         ParagraphList & pars)
414 {
415         // Clone the paragraphs within the selection.
416         pit_type startpit = range.from.pit();
417         pit_type endpit = range.to.pit();
418         ParagraphList const & ps_ = range.text()->paragraphs();
419         ParagraphList tmp_pars(boost::next(ps_.begin(), startpit),
420                 boost::next(ps_.begin(), endpit + 1));
421
422         // Remove the end of the last paragraph; afterwards, remove the
423         // beginning of the first paragraph. Keep this order - there may only
424         // be one paragraph!
425         Paragraph & back = tmp_pars.back();
426         back.eraseChars(range.to.pos(), back.size(), false);
427         Paragraph & front = tmp_pars.front();
428         front.eraseChars(0, range.from.pos(), false);
429
430         pars.insert(pars.begin(), tmp_pars.begin(), tmp_pars.end());
431 }
432
433
434 static bool equal(Inset const * i_o, Inset const * i_n)
435 {
436         if (!i_o || !i_n)
437                 return false;
438
439         // Different types of insets
440         if (i_o->lyxCode() != i_n->lyxCode())
441                 return false;
442
443         // Editable insets are assumed to be the same as they are of the 
444         // same type. If we later on decide that we insert them in the
445         // document as being unchanged, we will run the algorithm on the
446         // contents of the two insets.
447         // FIXME: This fails if the parameters of the insets differ.
448         // FIXME: We do not recurse into InsetTabulars.
449         // FIXME: We need methods inset->equivalent(inset).
450         if (i_o->editable() && !i_o->asInsetMath()
451                   && i_o->asInsetText())
452                 return true;
453
454         ostringstream o_os;
455         ostringstream n_os;
456         i_o->write(o_os);
457         i_n->write(n_os);
458         return o_os.str() == n_os.str();
459 }
460
461
462 static bool equal(DocIterator & o, DocIterator & n) {
463         Paragraph const & old_par = o.text()->getPar(o.pit());
464         Paragraph const & new_par = n.text()->getPar(n.pit());
465
466         char_type const c_o = old_par.getChar(o.pos());
467         char_type const c_n = new_par.getChar(n.pos());
468         if (c_o != c_n)
469                 return false;
470
471         if (old_par.isInset(o.pos())) {
472                 Inset const * i_o = old_par.getInset(o.pos());
473                 Inset const * i_n = new_par.getInset(n.pos());
474
475                 if (i_o && i_n)
476                         return equal(i_o, i_n);
477         }       
478
479         Font fo = old_par.getFontSettings(o.buffer()->params(), o.pos());
480         Font fn = new_par.getFontSettings(n.buffer()->params(), n.pos());
481         return fo == fn;
482 }
483
484
485 /// Traverses a snake in a certain direction. p points to a 
486 /// position in the old and new file and they are synchronously
487 /// moved along the snake. The function returns true if a snake
488 /// was found.
489 static bool traverse_snake(DocPair & p, DocRangePair const & range,
490         Direction direction)
491 {
492         bool ret = false;
493         DocPair const & p_end = 
494                 direction == Forward ? range.to() : range.from();
495
496         while (p != p_end) {
497                 if (direction == Backward)
498                         --p;
499                 if (!equal(p.o, p.n)) {
500                         if (direction == Backward)
501                                 ++p;
502                         return ret;
503                 }
504                 if (direction == Forward)
505                         ++p;
506                 ret = true;
507         }
508         return ret;
509 }
510
511
512 /////////////////////////////////////////////////////////////////////
513 //
514 // Compare::Impl
515 //
516 /////////////////////////////////////////////////////////////////////
517
518
519 void Compare::Impl::furthest_Dpath_kdiagonal(int D, int k,
520          DocRangePair const & rp, Direction direction)
521 {
522         compl_vector<DocIterator> * op = direction == Forward ? &ofp : &orp;
523         compl_vector<DocIterator> * np = direction == Forward ? &nfp : &nrp;
524         compl_vector<DocIterator> * os = direction == Forward ? &ofs : &ors;
525         compl_vector<DocIterator> * ns = direction == Forward ? &nfs : &nrs;
526
527         // A vertical step means stepping one character in the new document.
528         bool vertical_step = k == -D;
529         if (!vertical_step && k != D) {
530                 vertical_step = direction == Forward
531                         ? op->get(k - 1) < op->get(k + 1)
532                         : op->get(k - 1) > op->get(k + 1);
533         }
534
535         // Where do we take the step from ?
536         int const kk = vertical_step ? k + 1 : k - 1;
537         DocPair p(op->get(kk), np->get(kk));
538
539         // If D==0 we simulate a vertical step from (0,-1) by doing nothing.
540         if (D != 0) {
541                 // Take a step
542                 if (vertical_step && direction == Forward)
543                         step(p.n, rp.n.to, direction);
544                 else if (vertical_step && direction == Backward)
545                         step(p.n, rp.n.from, direction);
546                 else if (!vertical_step && direction == Forward)
547                         step(p.o, rp.o.to, direction);
548                 else if (!vertical_step && direction == Backward)
549                         step(p.o, rp.o.from, direction);
550         }       
551         
552         // Traverse snake
553         if (traverse_snake(p, rp, direction)) {
554                 // Record last snake
555                 os->set(k, p.o);
556                 ns->set(k, p.n);
557         } else {
558                 // Copy last snake from the previous step
559                 os->set(k, os->get(kk));
560                 ns->set(k, ns->get(kk));
561         }
562
563         //Record new position
564         op->set(k, p.o);
565         np->set(k, p.n);
566 }
567
568
569 bool Compare::Impl::overlap(int k, int D)
570 {
571         // To generalize for the forward and reverse checks
572         int kk = offset_reverse_diagonal_ - k;
573
574         // Can we have overlap ?
575         if (kk <= D && kk >= -D) {
576                 // Do we have overlap ?
577                 if (odd_offset_)
578                         return ofp.get(k) >= orp.get(kk) && nfp.get(k) >= nrp.get(kk);
579                 else
580                         return ofp.get(kk) >= orp.get(k) && nfp.get(kk) >= nrp.get(k);
581         }
582         return false;
583 }
584
585
586 Compare::Impl::SnakeResult Compare::Impl::retrieve_middle_snake(
587         int k, int D, Direction direction, DocPair & middle_snake)
588 {
589         compl_vector<DocIterator> * os = direction == Forward ? &ofs : &ors;
590         compl_vector<DocIterator> * ns = direction == Forward ? &nfs : &nrs;
591         compl_vector<DocIterator> * os_r = direction == Forward ? &ors : &ofs;
592         compl_vector<DocIterator> * ns_r = direction == Forward ? &nrs : &nfs;
593
594         // The diagonal while doing the backward search
595         int kk = -k + offset_reverse_diagonal_;
596
597         // Did we find a snake ?
598         if (os->get(k).empty() && os_r->get(kk).empty()) {
599                 // No, there is no snake at all, in which case
600                 // the length of the shortest edit script is M+N.
601                 LASSERT(2 * D - odd_offset_ == M_ + N_, /**/);
602                 return NoSnake;
603         } 
604         
605         if (os->get(k).empty()) {
606                 // Yes, but there is only 1 snake and we found it in the
607                 // reverse path.
608                 middle_snake.o = os_r->get(kk);
609                 middle_snake.n = ns_r->get(kk);
610                 return SingleSnake;
611         }
612
613         middle_snake.o = os->get(k);
614         middle_snake.n = ns->get(k);
615         return NormalSnake;
616 }
617
618
619 int Compare::Impl::find_middle_snake(DocRangePair const & rp,
620         DocPair & middle_snake)
621 {
622         // The lengths of the old and new chunks.
623         N_ = rp.o.length();
624         M_ = rp.n.length();
625
626         // Forward paths are centered around the 0-diagonal; reverse paths
627         // are centered around the diagonal N - M. (Delta in the article)
628         offset_reverse_diagonal_ = N_ - M_;
629
630         // If the offset is odd, only check for overlap while extending forward
631     // paths, otherwise only check while extending reverse paths.
632         odd_offset_ = (offset_reverse_diagonal_ % 2 != 0);
633
634         ofp.reset(rp.o.from);
635         nfp.reset(rp.n.from);
636         ofs.reset(DocIterator());
637         nfs.reset(DocIterator());
638         orp.reset(rp.o.to);
639         nrp.reset(rp.n.to);
640         ors.reset(DocIterator());
641         nrs.reset(DocIterator());
642
643         // D is the number of horizontal and vertical steps, i.e.
644         // different characters in the old and new chunk.
645         int const D_max = ceil(((double)M_ + N_)/2);
646         for (int D = 0; D <= D_max; ++D) {
647
648                 // Forward and reverse paths
649                 for (int f = 0; f < 2; ++f) {
650                         Direction direction = f == 0 ? Forward : Backward;
651
652                         // Diagonals between -D and D can be reached by a D-path
653                         for (int k = -D; k <= D; k += 2) {                      
654                                 // Find the furthest reaching D-path on this diagonal
655                                 furthest_Dpath_kdiagonal(D, k, rp, direction);
656
657                                 // Only check for overlap for forward paths if the offset is odd
658                                 // and only for reverse paths if the offset is even.
659                                 if (odd_offset_ == (direction == Forward)) {
660
661                                         // Do the forward and backward paths overlap ?
662                                         if (overlap(k, D - odd_offset_)) {
663                                                 retrieve_middle_snake(k, D, direction, middle_snake);
664                                                 return 2 * D - odd_offset_;
665                                         }
666                                 }
667                         }
668                 }
669         }
670         // This should never be reached
671         return -2;
672 }
673
674
675 bool Compare::Impl::diff(Buffer const * new_buf, Buffer const * old_buf,
676         Buffer const * dest_buf)
677 {
678         if (!new_buf || !old_buf || !dest_buf)
679                 return false;
680
681         old_buf_ = old_buf;
682         new_buf_ = new_buf;
683         dest_buf_ = dest_buf;
684         dest_pars_ = &dest_buf->inset().asInsetText()->paragraphs();
685         dest_pars_->clear();
686
687         recursion_level_ = 0;
688         nested_inset_level_ = 0;
689
690         DocRangePair rp(old_buf_, new_buf_);
691
692         DocPair from = rp.from();
693         traverse_snake(from, rp, Forward);
694         DocRangePair const snake(rp.from(), from);
695         process_snake(snake);
696         
697         // Start the recursive algorithm
698         diff_i(rp);
699
700         for (pit_type p = 0; p < (pit_type)dest_pars_->size(); ++p) {
701                 (*dest_pars_)[p].setBuffer(const_cast<Buffer &>(*dest_buf));
702                 (*dest_pars_)[p].setInsetOwner(&dest_buf_->inset());
703         }
704
705         return true;
706 }
707
708
709 void Compare::Impl::diff_i(DocRangePair const & rp)
710 {
711         // The middle snake
712         DocPair middle_snake;
713
714         // Divides the problem into two smaller problems, split around
715         // the snake in the middle.
716         int const L_ses = find_middle_snake(rp, middle_snake);
717
718         // Set maximum of progress bar
719         if (++recursion_level_ == 1)
720                 compare_.progressMax(L_ses);
721
722         // There are now three possibilities: the strings were the same,
723         // the strings were completely different, or we found a middle
724         // snake and we can split the string into two parts to process.
725         if (L_ses == 0)
726                 // Two the same strings (this must be a very rare case, because
727                 // usually this will be part of a snake adjacent to these strings).
728                 writeToDestBuffer(rp.o);
729
730         else if (middle_snake.o.empty()) {
731                 // Two totally different strings
732                 writeToDestBuffer(rp.o, Change::DELETED);
733                 writeToDestBuffer(rp.n, Change::INSERTED);
734
735         } else {
736                 // Retrieve the complete snake
737                 DocPair first_part_end = middle_snake;
738                 traverse_snake(first_part_end, rp, Backward);
739                 DocRangePair first_part(rp.from(), first_part_end);
740                         
741                 DocPair second_part_begin = middle_snake;
742                 traverse_snake(second_part_begin, rp, Forward);
743                 DocRangePair second_part(second_part_begin, rp.to());
744                                 
745                 // Split the string in three parts:
746                 // 1. in front of the snake
747                 diff_part(first_part);
748
749                 // 2. the snake itself, and
750                 DocRangePair const snake(first_part.to(), second_part.from());
751                 process_snake(snake);
752
753                 // 3. behind the snake.
754                 diff_part(second_part);
755         }
756         --recursion_level_;
757 }
758
759
760 void Compare::Impl::diff_part(DocRangePair const & rp)
761 {
762         // Is there a finite length string in both buffers, if not there
763         // is an empty string and we write the other one to the buffer.
764         if (!rp.o.empty() && !rp.n.empty())
765                 diff_i(rp);
766         
767         else if (!rp.o.empty())
768                 writeToDestBuffer(rp.o, Change::DELETED);
769
770         else if (!rp.n.empty())
771                 writeToDestBuffer(rp.n, Change::INSERTED);
772 }
773
774
775 void Compare::Impl::diff_inset(Inset * inset, DocPair const & p)
776 {
777         // Find the dociterators for the beginning and the
778         // end of the inset, for the old and new document.
779         DocRangePair const rp = stepIntoInset(p);
780
781         // Recurse into the inset. Temporarily replace the dest_pars
782         // paragraph list by the paragraph list of the nested inset.
783         ParagraphList * backup_dest_pars = dest_pars_;
784         dest_pars_ = &inset->asInsetText()->text().paragraphs();
785         dest_pars_->clear();
786         
787         ++nested_inset_level_;
788         diff_i(rp);
789         --nested_inset_level_;
790
791         dest_pars_ = backup_dest_pars;
792 }
793
794
795 void Compare::Impl::process_snake(DocRangePair const & rp)
796 {
797         ParagraphList pars;
798         get_paragraph_list(rp.o, pars);
799
800         // Find insets in this paragaph list
801         DocPair it = rp.from();
802         for (; it.o < rp.o.to; ++it) {
803                 Inset * inset = it.o.text()->getPar(it.o.pit()).getInset(it.o.pos());
804                 if (inset && inset->editable() && inset->asInsetText()) {
805                         // Find the inset in the paragraph list that will be pasted into
806                         // the final document. The contents of the inset will be replaced
807                         // by the output of the algorithm below.
808                         pit_type const pit = it.o.pit() - rp.o.from.pit();
809                         pos_type const pos = pit ? it.o.pos() : it.o.pos() - rp.o.from.pos();
810                         inset = pars[pit].getInset(pos);
811                         LASSERT(inset, /**/);
812                         diff_inset(inset, it);
813                 }
814         }
815         writeToDestBuffer(pars);
816 }
817
818
819 void Compare::Impl::writeToDestBuffer(DocRange const & range,
820         Change::Type type)
821 {
822         ParagraphList pars;
823         get_paragraph_list(range, pars);
824
825         pos_type size = 0;
826
827         // Set the change
828         ParagraphList::iterator it = pars.begin();
829         for (; it != pars.end(); ++it) {
830                 it->setChange(Change(type));
831                 size += it->size();
832         }
833
834         writeToDestBuffer(pars);
835
836         if (nested_inset_level_ == 0)
837                 compare_.progress(size);
838 }
839
840
841 void Compare::Impl::writeToDestBuffer(ParagraphList const & pars) const
842 {
843         pit_type const pit = dest_pars_->size() - 1;
844         dest_pars_->insert(dest_pars_->end(), pars.begin(), pars.end());
845         if (pit >= 0)
846                 mergeParagraph(dest_buf_->params(), *dest_pars_, pit);
847 }
848
849
850 #include "moc_Compare.cpp"
851
852 } // namespace lyx