]> git.lyx.org Git - features.git/blob - src/Undo.cpp
Better handling of multiple buffers in UndoGroupHelper
[features.git] / src / Undo.cpp
1 /**
2  * \file Undo.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup
7  * \author Lars Gullik Bjønnes
8  * \author John Levon
9  * \author André Pönitz
10  * \author Jürgen Vigna
11  * \author Abdelrazak Younes
12  *
13  * Full author contact details are available in file CREDITS.
14  */
15
16 #include <config.h>
17
18 #include "Undo.h"
19
20 #include "Buffer.h"
21 #include "BufferParams.h"
22 #include "buffer_funcs.h"
23 #include "Cursor.h"
24 #include "CutAndPaste.h"
25 #include "ErrorList.h"
26 #include "Paragraph.h"
27 #include "ParagraphList.h"
28 #include "Text.h"
29
30 #include "mathed/MathSupport.h"
31 #include "mathed/MathData.h"
32
33 #include "insets/Inset.h"
34 #include "insets/InsetText.h"
35
36 #include "support/debug.h"
37 #include "support/gettext.h"
38 #include "support/lassert.h"
39 #include "support/lyxtime.h"
40
41 #include <algorithm>
42 #include <deque>
43 #include <set>
44
45 using namespace std;
46 using namespace lyx::support;
47
48
49 namespace lyx {
50
51 /**
52 These are the elements put on the undo stack. Each object contains
53 complete paragraphs from some cell and sufficient information to
54 restore the cursor state.
55
56 The cell is given by a DocIterator pointing to this cell, the
57 'interesting' range of paragraphs by counting them from begin and end
58 of cell, respectively.
59
60 The cursor is also given as DocIterator and should point to some place
61 in the stored paragraph range. In case of math, we simply store the
62 whole cell, as there usually is just a simple paragraph in a cell.
63
64 The idea is to store the contents of 'interesting' paragraphs in some
65 structure ('Undo') _before_ it is changed in some edit operation.
66 Obviously, the stored range should be as small as possible. However,
67 there is a lower limit: The StableDocIterator stored in the undo class
68 must be valid after the changes, too, as it will used as a pointer
69 where to insert the stored bits when performining undo.
70 */
71 struct UndoElement
72 {
73         ///
74         UndoElement(UndoKind kin, CursorData const & cb,
75                     StableDocIterator const & cel,
76                     pit_type fro, pit_type en, ParagraphList * pl, MathData * ar,
77                     bool lc, size_t gid) :
78                 kind(kin), cur_before(cb), cell(cel), from(fro), end(en),
79                 pars(pl), array(ar), bparams(0),
80                 lyx_clean(lc), group_id(gid), time(current_time())
81                 {
82                 }
83         ///
84         UndoElement(CursorData const & cb, BufferParams const & bp,
85                                 bool lc, size_t gid) :
86                 kind(ATOMIC_UNDO), cur_before(cb), cell(), from(0), end(0),
87                 pars(0), array(0), bparams(new BufferParams(bp)),
88                 lyx_clean(lc), group_id(gid), time(current_time())
89         {
90         }
91         ///
92         UndoElement(UndoElement const & ue) : time(current_time())
93         {
94                 kind = ue.kind;
95                 cur_before = ue.cur_before;
96                 cur_after = ue.cur_after;
97                 cell = ue.cell;
98                 from = ue.from;
99                 end = ue.end;
100                 pars = ue.pars;
101                 array = ue.array;
102                 bparams = ue.bparams
103                         ? new BufferParams(*ue.bparams) : 0;
104                 lyx_clean = ue.lyx_clean;
105                 group_id = ue.group_id;
106         }
107         ///
108         ~UndoElement()
109         {
110                 if (bparams)
111                         delete bparams;
112         }
113         /// Which kind of operation are we recording for?
114         UndoKind kind;
115         /// the position of the cursor before recordUndo
116         CursorData cur_before;
117         /// the position of the cursor at the end of the undo group
118         CursorData cur_after;
119         /// the position of the cell described
120         StableDocIterator cell;
121         /// counted from begin of cell
122         pit_type from;
123         /// complement to end of this cell
124         pit_type end;
125         /// the contents of the saved Paragraphs (for texted)
126         ParagraphList * pars;
127         /// the contents of the saved MathData (for mathed)
128         MathData * array;
129         /// Only used in case of params undo
130         BufferParams const * bparams;
131         /// Was the buffer clean at this point?
132         bool lyx_clean;
133         /// the element's group id
134         size_t group_id;
135         /// timestamp
136         time_t time;
137 private:
138         /// Protect construction
139         UndoElement();
140 };
141
142
143 class UndoElementStack
144 {
145 public:
146         /// limit is the maximum size of the stack
147         UndoElementStack(size_t limit = 100) { limit_ = limit; }
148         /// limit is the maximum size of the stack
149         ~UndoElementStack() { clear(); }
150
151         /// Return the top element.
152         UndoElement & top() { return c_.front(); }
153
154         /// Pop and throw away the top element.
155         void pop() { c_.pop_front(); }
156
157         /// Return true if the stack is empty.
158         bool empty() const { return c_.empty(); }
159
160         /// Clear all elements, deleting them.
161         void clear() {
162                 for (size_t i = 0; i != c_.size(); ++i) {
163                         delete c_[i].array;
164                         delete c_[i].pars;
165                 }
166                 c_.clear();
167         }
168
169         /// Push an item on to the stack, deleting the bottom group on
170         /// overflow.
171         void push(UndoElement const & v) {
172                 // Remove some entries if the limit has been reached.
173                 // However, if the only group on the stack is the one
174                 // we are currently populating, do nothing.
175                 if (c_.size() >= limit_
176                     && c_.front().group_id != v.group_id) {
177                         // remove a whole group at once.
178                         const size_t gid = c_.back().group_id;
179                         while (!c_.empty() && c_.back().group_id == gid)
180                                 c_.pop_back();
181                 }
182                 c_.push_front(v);
183         }
184
185         /// Mark all the elements of the stack as dirty
186         void markDirty() {
187                 for (size_t i = 0; i != c_.size(); ++i)
188                         c_[i].lyx_clean = false;
189         }
190
191 private:
192         /// Internal contents.
193         std::deque<UndoElement> c_;
194         /// The maximum number elements stored.
195         size_t limit_;
196 };
197
198
199 struct Undo::Private
200 {
201         Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true),
202                                    group_id_(0), group_level_(0) {}
203
204         // Do one undo/redo step
205         void doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack,
206                               UndoElementStack & otherStack);
207         // Apply one undo/redo group. Returns false if no undo possible.
208         bool textUndoOrRedo(CursorData & cur, bool isUndoOperation);
209
210         ///
211         void doRecordUndo(UndoKind kind,
212                 DocIterator const & cell,
213                 pit_type first_pit,
214                 pit_type last_pit,
215                 CursorData const & cur,
216                 UndoElementStack & stack);
217         ///
218         void recordUndo(UndoKind kind,
219                 DocIterator const & cell,
220                 pit_type first_pit,
221                 pit_type last_pit,
222                 CursorData const & cur);
223         ///
224         void doRecordUndoBufferParams(CursorData const & cur, UndoElementStack & stack);
225         ///
226         void recordUndoBufferParams(CursorData const & cur);
227
228         ///
229         Buffer & buffer_;
230         /// Undo stack.
231         UndoElementStack undostack_;
232         /// Redo stack.
233         UndoElementStack redostack_;
234
235         /// The flag used by Undo::finishUndo().
236         bool undo_finished_;
237
238         /// Current group Id.
239         size_t group_id_;
240         /// Current group nesting nevel.
241         size_t group_level_;
242         /// the position of cursor before the group was created
243         CursorData group_cur_before_;
244 };
245
246
247 /////////////////////////////////////////////////////////////////////
248 //
249 // Undo
250 //
251 /////////////////////////////////////////////////////////////////////
252
253
254 Undo::Undo(Buffer & buffer)
255         : d(new Undo::Private(buffer))
256 {}
257
258
259 Undo::~Undo()
260 {
261         delete d;
262 }
263
264
265 void Undo::clear()
266 {
267         d->undostack_.clear();
268         d->redostack_.clear();
269         d->undo_finished_ = true;
270         // We used to do that, but I believe it is better to keep
271         // groups (only used in Buffer::reload for now (JMarc)
272         //d->group_id_ = 0;
273         //d->group_level_ = 0;
274 }
275
276
277 bool Undo::hasUndoStack() const
278 {
279         return !d->undostack_.empty();
280 }
281
282
283 bool Undo::hasRedoStack() const
284 {
285         return !d->redostack_.empty();
286 }
287
288
289 void Undo::markDirty()
290 {
291         d->undo_finished_ = true;
292         d->undostack_.markDirty();
293         d->redostack_.markDirty();
294 }
295
296
297 /////////////////////////////////////////////////////////////////////
298 //
299 // Undo::Private
300 //
301 ///////////////////////////////////////////////////////////////////////
302
303 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
304 {
305         StableDocIterator tmpi2 = i2;
306         tmpi2.pos() = i1.pos();
307         return i1 == tmpi2;
308 }
309
310
311 void Undo::Private::doRecordUndo(UndoKind kind,
312         DocIterator const & cell,
313         pit_type first_pit, pit_type last_pit,
314         CursorData const & cur_before,
315         UndoElementStack & stack)
316 {
317         if (!group_level_) {
318                 LYXERR0("There is no group open (creating one)");
319                 ++group_id_;
320         }
321
322         if (first_pit > last_pit)
323                 swap(first_pit, last_pit);
324
325         // Undo::ATOMIC are always recorded (no overlapping there).
326         // As nobody wants all removed character appear one by one when undoing,
327         // we want combine 'similar' non-ATOMIC undo recordings to one.
328         pit_type from = first_pit;
329         pit_type end = cell.lastpit() - last_pit;
330         if (!undo_finished_
331             && kind != ATOMIC_UNDO
332             && !stack.empty()
333             && !stack.top().bparams
334             && samePar(stack.top().cell, cell)
335             && stack.top().kind == kind
336             && stack.top().from == from
337             && stack.top().end == end
338             && stack.top().cur_after == cur_before
339             && current_time() - stack.top().time <= 2) {
340                 // reset cur_after; it will be filled correctly by endUndoGroup.
341                 stack.top().cur_after = CursorData();
342                 // update the timestamp of the undo element
343                 stack.top().time = current_time();
344                 return;
345         }
346
347         LYXERR(Debug::UNDO, "Create undo element of group " << group_id_);
348         // create the position information of the Undo entry
349         UndoElement undo(kind,
350               group_cur_before_.empty() ? cur_before : group_cur_before_,
351               cell, from, end, 0, 0, buffer_.isClean(), group_id_);
352
353         // fill in the real data to be saved
354         if (cell.inMathed()) {
355                 // simply use the whole cell
356                 MathData & ar = cell.cell();
357                 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
358         } else {
359                 // some more effort needed here as 'the whole cell' of the
360                 // main Text _is_ the whole document.
361                 // record the relevant paragraphs
362                 Text const * text = cell.text();
363                 LBUFERR(text);
364                 ParagraphList const & plist = text->paragraphs();
365                 ParagraphList::const_iterator first = plist.begin();
366                 advance(first, first_pit);
367                 ParagraphList::const_iterator last = plist.begin();
368                 advance(last, last_pit + 1);
369                 undo.pars = new ParagraphList(first, last);
370         }
371
372         // push the undo entry to undo stack
373         stack.push(undo);
374         //lyxerr << "undo record: " << stack.top() << endl;
375 }
376
377
378 void Undo::Private::recordUndo(UndoKind kind,
379                                DocIterator const & cell,
380                                pit_type first_pit, pit_type last_pit,
381                                CursorData const & cur)
382 {
383         LASSERT(first_pit <= cell.lastpit(), return);
384         LASSERT(last_pit <= cell.lastpit(), return);
385
386         doRecordUndo(kind, cell, first_pit, last_pit, cur,
387                 undostack_);
388
389         // next time we'll try again to combine entries if possible
390         undo_finished_ = false;
391
392         // If we ran recordUndo, it means that we plan to change the buffer
393         buffer_.markDirty();
394
395         redostack_.clear();
396 }
397
398
399 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
400                                              UndoElementStack & stack)
401 {
402         if (!group_level_) {
403                 LYXERR0("There is no group open (creating one)");
404                 ++group_id_;
405         }
406
407         LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id_);
408         // create the position information of the Undo entry
409         UndoElement undo(group_cur_before_.empty() ? cur_before : group_cur_before_,
410                      buffer_.params(), buffer_.isClean(),
411                                          group_id_);
412
413         // push the undo entry to undo stack
414         stack.push(undo);
415 }
416
417
418 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
419 {
420         doRecordUndoBufferParams(cur, undostack_);
421
422         // next time we'll try again to combine entries if possible
423         undo_finished_ = false;
424
425         // If we ran recordUndo, it means that we plan to change the buffer
426         buffer_.markDirty();
427
428         redostack_.clear();
429 }
430
431
432 void Undo::Private::doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
433 {
434         // Adjust undo stack and get hold of current undo data.
435         UndoElement & undo = stack.top();
436         LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
437         // We'll pop the stack only when we're done with this element. So do NOT
438         // try to return early.
439
440         // We will store in otherstack the part of the document under 'undo'
441         DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
442
443         if (undo.bparams)
444                 doRecordUndoBufferParams(undo.cur_after, otherstack);
445         else {
446                 LATTEST(undo.end <= cell_dit.lastpit());
447                 doRecordUndo(ATOMIC_UNDO, cell_dit,
448                                          undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
449                                          otherstack);
450         }
451         otherstack.top().cur_after = undo.cur_before;
452
453         // This does the actual undo/redo.
454         //LYXERR0("undo, performing: " << undo);
455         DocIterator dit = undo.cell.asDocIterator(&buffer_);
456         if (undo.bparams) {
457                 // This is a params undo element
458                 delete otherstack.top().bparams;
459                 otherstack.top().bparams = new BufferParams(buffer_.params());
460                 DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
461                 buffer_.params() = *undo.bparams;
462                 // The error list is not supposed to be helpful here.
463                 ErrorList el;
464                 cap::switchBetweenClasses(olddc, buffer_.params().documentClassPtr(),
465                         static_cast<InsetText &>(buffer_.inset()), el);
466                 LATTEST(el.empty());
467         } else if (dit.inMathed()) {
468                 // We stored the full cell here as there is not much to be
469                 // gained by storing just 'a few' paragraphs (most if not
470                 // all math inset cells have just one paragraph!)
471                 //LYXERR0("undo.array: " << *undo.array);
472                 LBUFERR(undo.array);
473                 dit.cell().swap(*undo.array);
474                 delete undo.array;
475                 undo.array = 0;
476         } else {
477                 // Some finer machinery is needed here.
478                 Text * text = dit.text();
479                 LBUFERR(text);
480                 LBUFERR(undo.pars);
481                 ParagraphList & plist = text->paragraphs();
482
483                 // remove new stuff between first and last
484                 ParagraphList::iterator first = plist.begin();
485                 advance(first, undo.from);
486                 ParagraphList::iterator last = plist.begin();
487                 advance(last, plist.size() - undo.end);
488                 plist.erase(first, last);
489
490                 // re-insert old stuff instead
491                 first = plist.begin();
492                 advance(first, undo.from);
493
494                 // this ugly stuff is needed until we get rid of the
495                 // inset_owner backpointer
496                 ParagraphList::iterator pit = undo.pars->begin();
497                 ParagraphList::iterator const end = undo.pars->end();
498                 for (; pit != end; ++pit)
499                         pit->setInsetOwner(dit.realInset());
500                 plist.insert(first, undo.pars->begin(), undo.pars->end());
501
502                 // set the buffers for insets we created
503                 ParagraphList::iterator fpit = plist.begin();
504                 advance(fpit, undo.from);
505                 ParagraphList::iterator fend = fpit;
506                 advance(fend, undo.pars->size());
507                 for (; fpit != fend; ++fpit)
508                         fpit->setInsetBuffers(buffer_);
509
510                 delete undo.pars;
511                 undo.pars = 0;
512         }
513
514         // We'll clean up in release mode.
515         LASSERT(undo.pars == 0, undo.pars = 0);
516         LASSERT(undo.array == 0, undo.array = 0);
517
518         if (!undo.cur_before.empty())
519                 cur = undo.cur_before;
520         if (undo.lyx_clean)
521                 buffer_.markClean();
522         else
523                 buffer_.markDirty();
524         // Now that we're done with undo, we pop it off the stack.
525         stack.pop();
526 }
527
528
529 bool Undo::Private::textUndoOrRedo(CursorData & cur, bool isUndoOperation)
530 {
531         undo_finished_ = true;
532
533         UndoElementStack & stack = isUndoOperation ?  undostack_ : redostack_;
534
535         if (stack.empty())
536                 // Nothing to do.
537                 return false;
538
539         UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
540
541         const size_t gid = stack.top().group_id;
542         while (!stack.empty() && stack.top().group_id == gid)
543                 doTextUndoOrRedo(cur, stack, otherstack);
544         return true;
545 }
546
547
548 void Undo::finishUndo()
549 {
550         // Make sure the next operation will be stored.
551         d->undo_finished_ = true;
552 }
553
554
555 bool Undo::textUndo(CursorData & cur)
556 {
557         return d->textUndoOrRedo(cur, true);
558 }
559
560
561 bool Undo::textRedo(CursorData & cur)
562 {
563         return d->textUndoOrRedo(cur, false);
564 }
565
566
567 void Undo::beginUndoGroup()
568 {
569         if (d->group_level_ == 0) {
570                 // create a new group
571                 ++d->group_id_;
572                 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id_);
573         }
574         ++d->group_level_;
575 }
576
577
578 void Undo::beginUndoGroup(CursorData const & cur_before)
579 {
580         beginUndoGroup();
581         if (d->group_cur_before_.empty())
582                 d->group_cur_before_ = cur_before;
583 }
584
585
586 void Undo::endUndoGroup()
587 {
588         if (d->group_level_ == 0) {
589                 LYXERR0("There is no undo group to end here");
590                 return;
591         }
592         --d->group_level_;
593         if (d->group_level_ == 0) {
594                 // real end of the group
595                 d->group_cur_before_ = CursorData();
596                 LYXERR(Debug::UNDO, "-------End of group " << d->group_id_);
597         }
598 }
599
600
601 void Undo::endUndoGroup(CursorData const & cur_after)
602 {
603         endUndoGroup();
604         if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
605                 d->undostack_.top().cur_after = cur_after;
606 }
607
608
609 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
610 {
611         d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
612 }
613
614
615 void Undo::recordUndo(CursorData const & cur, pit_type from, pit_type to)
616 {
617         d->recordUndo(ATOMIC_UNDO, cur, from, to, cur);
618 }
619
620
621 void Undo::recordUndoInset(CursorData const & cur, Inset const * inset)
622 {
623         if (!inset || inset == &cur.inset()) {
624                 DocIterator c = cur;
625                 c.pop_back();
626                 d->recordUndo(ATOMIC_UNDO, c, c.pit(), c.pit(), cur);
627         } else if (inset == cur.nextInset())
628                 recordUndo(cur);
629         else
630                 LYXERR0("Inset not found, no undo stack added.");
631 }
632
633
634 void Undo::recordUndoBufferParams(CursorData const & cur)
635 {
636         d->recordUndoBufferParams(cur);
637 }
638
639
640 void Undo::recordUndoFullBuffer(CursorData const & cur)
641 {
642         // This one may happen outside of the main undo group, so we
643         // put it in its own subgroup to avoid complaints.
644         beginUndoGroup();
645         d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
646                       0, d->buffer_.paragraphs().size() - 1, cur);
647         d->recordUndoBufferParams(cur);
648         endUndoGroup();
649 }
650
651 /// UndoGroupHelper class stuff
652
653 class UndoGroupHelper::Impl {
654         friend class UndoGroupHelper;
655         set<Buffer *> buffers_;
656 };
657
658
659 UndoGroupHelper::UndoGroupHelper(Buffer * buf) : d(new UndoGroupHelper::Impl)
660 {
661         resetBuffer(buf);
662 }
663
664
665 UndoGroupHelper::~UndoGroupHelper()
666 {
667         for (Buffer * buf : d->buffers_)
668                 buf->undo().endUndoGroup();
669         delete d;
670 }
671
672 void UndoGroupHelper::resetBuffer(Buffer * buf)
673 {
674         if (buf && d->buffers_.count(buf) == 0) {
675                 d->buffers_.insert(buf);
676                 buf->undo().beginUndoGroup();
677         }
678 }
679
680
681 } // namespace lyx