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