]> git.lyx.org Git - features.git/blob - src/Undo.cpp
Clean includes using the output of iwyu tool
[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/MathData.h"
31
32 #include "insets/Inset.h"
33 #include "insets/InsetText.h"
34
35 #include "support/debug.h"
36 #include "support/lassert.h"
37 #include "support/lyxtime.h"
38
39 #include <algorithm>
40 #include <deque>
41 #include <set>
42
43 using namespace std;
44 using namespace lyx::support;
45
46
47 namespace lyx {
48
49 /**
50 These are the elements put on the undo stack. Each object contains
51 complete paragraphs from some cell and sufficient information to
52 restore the cursor state.
53
54 The cell is given by a DocIterator pointing to this cell, the
55 'interesting' range of paragraphs by counting them from begin and end
56 of cell, respectively.
57
58 The cursor is also given as DocIterator and should point to some place
59 in the stored paragraph range. In case of math, we simply store the
60 whole cell, as there usually is just a simple paragraph in a cell.
61
62 The idea is to store the contents of 'interesting' paragraphs in some
63 structure ('Undo') _before_ it is changed in some edit operation.
64 Obviously, the stored range should be as small as possible. However,
65 there is a lower limit: The StableDocIterator stored in the undo class
66 must be valid after the changes, too, as it will used as a pointer
67 where to insert the stored bits when performining undo.
68 */
69 struct UndoElement
70 {
71         ///
72         UndoElement(UndoKind kin, CursorData const & cb,
73                     StableDocIterator const & cel,
74                     pit_type fro, pit_type en, ParagraphList * pl, MathData * ar,
75                     bool lc, size_t gid) :
76                 cur_before(cb), cell(cel), from(fro), end(en),
77                 pars(pl), array(ar), bparams(nullptr),
78                 group_id(gid), time(current_time()), kind(kin), lyx_clean(lc)
79                 {}
80         ///
81         UndoElement(CursorData const & cb, BufferParams const & bp,
82                                 bool lc, size_t gid) :
83                 cur_before(cb), cell(), from(0), end(0),
84                 pars(nullptr), array(nullptr), bparams(new BufferParams(bp)),
85                 group_id(gid), time(current_time()), kind(ATOMIC_UNDO), lyx_clean(lc)
86         {}
87         ///
88         UndoElement(UndoElement const & ue) :
89                 cur_before(ue.cur_before), cur_after(ue.cur_after),
90                 cell(ue.cell), from(ue.from), end(ue.end),
91                 pars(ue.pars), array(ue.array),
92                 bparams(ue.bparams ? new BufferParams(*ue.bparams) : nullptr),
93                 group_id(ue.group_id), time(current_time()), kind(ue.kind),
94                 lyx_clean(ue.lyx_clean)
95                 {}
96         ///
97         ~UndoElement()
98         {
99                 if (bparams)
100                         delete bparams;
101         }
102         /// the position of the cursor before recordUndo
103         CursorData cur_before;
104         /// the position of the cursor at the end of the undo group
105         CursorData cur_after;
106         /// the position of the cell described
107         StableDocIterator cell;
108         /// counted from begin of cell
109         pit_type from;
110         /// complement to end of this cell
111         pit_type end;
112         /// the contents of the saved Paragraphs (for texted)
113         ParagraphList * pars;
114         /// the contents of the saved MathData (for mathed)
115         MathData * array;
116         /// Only used in case of params undo
117         BufferParams const * bparams;
118         /// the element's group id
119         size_t group_id;
120         /// timestamp
121         time_t time;
122         /// Which kind of operation are we recording for?
123         UndoKind kind;
124         /// Was the buffer clean at this point?
125         bool lyx_clean;
126 private:
127         /// Protect construction
128         UndoElement();
129 };
130
131
132 class UndoElementStack
133 {
134 public:
135         /// limit is the maximum size of the stack
136         UndoElementStack(size_t limit = 100) { limit_ = limit; }
137         /// limit is the maximum size of the stack
138         ~UndoElementStack() { clear(); }
139
140         /// Return the top element.
141         UndoElement & top() { return c_.front(); }
142
143         /// Pop and throw away the top element.
144         void pop() { c_.pop_front(); }
145
146         /// Return true if the stack is empty.
147         bool empty() const { return c_.empty(); }
148
149         /// Clear all elements, deleting them.
150         void clear() {
151                 for (size_t i = 0; i != c_.size(); ++i) {
152                         delete c_[i].array;
153                         delete c_[i].pars;
154                 }
155                 c_.clear();
156         }
157
158         /// Push an item on to the stack, deleting the bottom group on
159         /// overflow.
160         void push(UndoElement const & v) {
161                 // Remove some entries if the limit has been reached.
162                 // However, if the only group on the stack is the one
163                 // we are currently populating, do nothing.
164                 if (c_.size() >= limit_
165                     && c_.front().group_id != v.group_id) {
166                         // remove a whole group at once.
167                         const size_t gid = c_.back().group_id;
168                         while (!c_.empty() && c_.back().group_id == gid)
169                                 c_.pop_back();
170                 }
171                 c_.push_front(v);
172         }
173
174         /// Mark all the elements of the stack as dirty
175         void markDirty() {
176                 for (size_t i = 0; i != c_.size(); ++i)
177                         c_[i].lyx_clean = false;
178         }
179
180 private:
181         /// Internal contents.
182         std::deque<UndoElement> c_;
183         /// The maximum number elements stored.
184         size_t limit_;
185 };
186
187
188 struct Undo::Private
189 {
190         Private(Buffer & buffer) : buffer_(buffer),
191                 group_id_(0), group_level_(0), undo_finished_(true) {}
192
193         // Do one undo/redo step
194         void doUndoRedoAction(CursorData & cur, UndoElementStack & stack,
195                               UndoElementStack & otherStack);
196         // Apply one undo/redo group. Returns false if no undo possible.
197         bool undoRedoAction(CursorData & cur, bool isUndoOperation);
198
199         ///
200         void doRecordUndo(UndoKind kind,
201                 DocIterator const & cell,
202                 pit_type first_pit,
203                 pit_type last_pit,
204                 CursorData const & cur,
205                 UndoElementStack & stack);
206         ///
207         void recordUndo(UndoKind kind,
208                 DocIterator const & cell,
209                 pit_type first_pit,
210                 pit_type last_pit,
211                 CursorData const & cur);
212         ///
213         void doRecordUndoBufferParams(CursorData const & cur, UndoElementStack & stack);
214         ///
215         void recordUndoBufferParams(CursorData const & cur);
216
217         ///
218         Buffer & buffer_;
219         /// Undo stack.
220         UndoElementStack undostack_;
221         /// Redo stack.
222         UndoElementStack redostack_;
223
224         /// Current group Id.
225         size_t group_id_;
226         /// Current group nesting nevel.
227         size_t group_level_;
228         /// the position of cursor before the group was created
229         CursorData group_cur_before_;
230
231         /// The flag used by Undo::finishUndo().
232         bool undo_finished_;
233
234 };
235
236
237 /////////////////////////////////////////////////////////////////////
238 //
239 // Undo
240 //
241 /////////////////////////////////////////////////////////////////////
242
243
244 Undo::Undo(Buffer & buffer)
245         : d(new Undo::Private(buffer))
246 {}
247
248
249 Undo::~Undo()
250 {
251         delete d;
252 }
253
254
255 void Undo::clear()
256 {
257         d->undostack_.clear();
258         d->redostack_.clear();
259         d->undo_finished_ = true;
260         // We used to do that, but I believe it is better to keep
261         // groups (only used in Buffer::reload for now (JMarc)
262         //d->group_id_ = 0;
263         //d->group_level_ = 0;
264 }
265
266
267 bool Undo::hasUndoStack() const
268 {
269         return !d->undostack_.empty();
270 }
271
272
273 bool Undo::hasRedoStack() const
274 {
275         return !d->redostack_.empty();
276 }
277
278
279 void Undo::markDirty()
280 {
281         d->undo_finished_ = true;
282         d->undostack_.markDirty();
283         d->redostack_.markDirty();
284 }
285
286
287 /////////////////////////////////////////////////////////////////////
288 //
289 // Undo::Private
290 //
291 ///////////////////////////////////////////////////////////////////////
292
293 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
294 {
295         StableDocIterator tmpi2 = i2;
296         tmpi2.pos() = i1.pos();
297         return i1 == tmpi2;
298 }
299
300
301 void Undo::Private::doRecordUndo(UndoKind kind,
302         DocIterator const & cell,
303         pit_type first_pit, pit_type last_pit,
304         CursorData const & cur_before,
305         UndoElementStack & stack)
306 {
307         if (!group_level_) {
308                 LYXERR0("There is no group open (creating one)");
309                 ++group_id_;
310         }
311
312         if (first_pit > last_pit)
313                 swap(first_pit, last_pit);
314
315         // Undo::ATOMIC are always recorded (no overlapping there).
316         // As nobody wants all removed character appear one by one when undoing,
317         // we want combine 'similar' non-ATOMIC undo recordings to one.
318         pit_type from = first_pit;
319         pit_type end = cell.lastpit() - last_pit;
320         if (!undo_finished_
321             && kind != ATOMIC_UNDO
322             && !stack.empty()
323             && !stack.top().bparams
324             && samePar(stack.top().cell, cell)
325             && stack.top().kind == kind
326             && stack.top().from == from
327             && stack.top().end == end
328             && stack.top().cur_after == cur_before
329             && current_time() - stack.top().time <= 2) {
330                 // reset cur_after; it will be filled correctly by endUndoGroup.
331                 stack.top().cur_after = CursorData();
332                 // update the timestamp of the undo element
333                 stack.top().time = current_time();
334                 return;
335         }
336
337         LYXERR(Debug::UNDO, "Create undo element of group " << group_id_);
338         // create the position information of the Undo entry
339         UndoElement undo(kind,
340               group_cur_before_.empty() ? cur_before : group_cur_before_,
341               cell, from, end, nullptr, nullptr, buffer_.isClean(), group_id_);
342
343         // fill in the real data to be saved
344         if (cell.inMathed()) {
345                 // simply use the whole cell
346                 MathData & ar = cell.cell();
347                 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
348         } else {
349                 // some more effort needed here as 'the whole cell' of the
350                 // main Text _is_ the whole document.
351                 // record the relevant paragraphs
352                 Text const * text = cell.text();
353                 LBUFERR(text);
354                 ParagraphList const & plist = text->paragraphs();
355                 ParagraphList::const_iterator first = plist.begin();
356                 advance(first, first_pit);
357                 ParagraphList::const_iterator last = plist.begin();
358                 advance(last, last_pit + 1);
359                 undo.pars = new ParagraphList(first, last);
360         }
361
362         // push the undo entry to undo stack
363         stack.push(undo);
364         //lyxerr << "undo record: " << stack.top() << endl;
365 }
366
367
368 void Undo::Private::recordUndo(UndoKind kind,
369                                DocIterator const & cell,
370                                pit_type first_pit, pit_type last_pit,
371                                CursorData const & cur)
372 {
373         LASSERT(first_pit <= cell.lastpit(), return);
374         LASSERT(last_pit <= cell.lastpit(), return);
375
376         doRecordUndo(kind, cell, first_pit, last_pit, cur,
377                 undostack_);
378
379         // next time we'll try again to combine entries if possible
380         undo_finished_ = false;
381
382         // If we ran recordUndo, it means that we plan to change the buffer
383         buffer_.markDirty();
384
385         redostack_.clear();
386 }
387
388
389 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
390                                              UndoElementStack & stack)
391 {
392         if (!group_level_) {
393                 LYXERR0("There is no group open (creating one)");
394                 ++group_id_;
395         }
396
397         LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id_);
398         // create the position information of the Undo entry
399         UndoElement undo(group_cur_before_.empty() ? cur_before : group_cur_before_,
400                      buffer_.params(), buffer_.isClean(),
401                                          group_id_);
402
403         // push the undo entry to undo stack
404         stack.push(undo);
405 }
406
407
408 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
409 {
410         doRecordUndoBufferParams(cur, undostack_);
411
412         // next time we'll try again to combine entries if possible
413         undo_finished_ = false;
414
415         // If we ran recordUndo, it means that we plan to change the buffer
416         buffer_.markDirty();
417
418         redostack_.clear();
419 }
420
421
422 void Undo::Private::doUndoRedoAction(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
423 {
424         // Adjust undo stack and get hold of current undo data.
425         UndoElement & undo = stack.top();
426         LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
427         // We'll pop the stack only when we're done with this element. So do NOT
428         // try to return early.
429
430         // We will store in otherstack the part of the document under 'undo'
431         DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
432
433         if (undo.bparams)
434                 doRecordUndoBufferParams(undo.cur_after, otherstack);
435         else {
436                 LATTEST(undo.end <= cell_dit.lastpit());
437                 doRecordUndo(ATOMIC_UNDO, cell_dit,
438                                          undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
439                                          otherstack);
440         }
441         otherstack.top().cur_after = undo.cur_before;
442
443         // This does the actual undo/redo.
444         //LYXERR0("undo, performing: " << undo);
445         DocIterator dit = undo.cell.asDocIterator(&buffer_);
446         if (undo.bparams) {
447                 // This is a params undo element
448                 delete otherstack.top().bparams;
449                 otherstack.top().bparams = new BufferParams(buffer_.params());
450                 DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
451                 buffer_.params() = *undo.bparams;
452                 // The error list is not supposed to be helpful here.
453                 ErrorList el;
454                 cap::switchBetweenClasses(olddc, buffer_.params().documentClassPtr(),
455                         static_cast<InsetText &>(buffer_.inset()), el);
456                 LATTEST(el.empty());
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