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