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