]> git.lyx.org Git - lyx.git/blob - src/Undo.cpp
MathML: more consistency between DocBook and XHTML.
[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 "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                 cap::switchBetweenClasses(olddc, buffer_.params().documentClassPtr(),
454                         static_cast<InsetText &>(buffer_.inset()));
455         } else if (dit.inMathed()) {
456                 // We stored the full cell here as there is not much to be
457                 // gained by storing just 'a few' paragraphs (most if not
458                 // all math inset cells have just one paragraph!)
459                 //LYXERR0("undo.array: " << *undo.array);
460                 LBUFERR(undo.array);
461                 dit.cell().swap(*undo.array);
462                 dit.inset().setBuffer(buffer_);
463                 delete undo.array;
464                 undo.array = nullptr;
465         } else {
466                 // Some finer machinery is needed here.
467                 Text * text = dit.text();
468                 LBUFERR(text);
469                 LBUFERR(undo.pars);
470                 ParagraphList & plist = text->paragraphs();
471
472                 // remove new stuff between first and last
473                 ParagraphList::iterator first = plist.begin();
474                 advance(first, undo.from);
475                 ParagraphList::iterator last = plist.begin();
476                 advance(last, plist.size() - undo.end);
477                 plist.erase(first, last);
478
479                 // re-insert old stuff instead
480                 first = plist.begin();
481                 advance(first, undo.from);
482
483                 // this ugly stuff is needed until we get rid of the
484                 // inset_owner backpointer
485                 ParagraphList::iterator pit = undo.pars->begin();
486                 ParagraphList::iterator const end = undo.pars->end();
487                 for (; pit != end; ++pit)
488                         pit->setInsetOwner(dit.realInset());
489                 plist.insert(first, undo.pars->begin(), undo.pars->end());
490
491                 // set the buffers for insets we created
492                 ParagraphList::iterator fpit = plist.begin();
493                 advance(fpit, undo.from);
494                 ParagraphList::iterator fend = fpit;
495                 advance(fend, undo.pars->size());
496                 for (; fpit != fend; ++fpit)
497                         fpit->setInsetBuffers(buffer_);
498
499                 delete undo.pars;
500                 undo.pars = nullptr;
501         }
502
503         // We'll clean up in release mode.
504         LASSERT(undo.pars == nullptr, undo.pars = nullptr);
505         LASSERT(undo.array == nullptr, undo.array = nullptr);
506
507         if (!undo.cur_before.empty())
508                 cur = undo.cur_before;
509         if (undo.lyx_clean)
510                 buffer_.markClean();
511         else
512                 buffer_.markDirty();
513         // Now that we're done with undo, we pop it off the stack.
514         stack.pop();
515 }
516
517
518 bool Undo::Private::undoRedoAction(CursorData & cur, bool isUndoOperation)
519 {
520         undo_finished_ = true;
521
522         UndoElementStack & stack = isUndoOperation ?  undostack_ : redostack_;
523
524         if (stack.empty())
525                 // Nothing to do.
526                 return false;
527
528         UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
529
530         const size_t gid = stack.top().group_id;
531         while (!stack.empty() && stack.top().group_id == gid)
532                 doUndoRedoAction(cur, stack, otherstack);
533         return true;
534 }
535
536
537 void Undo::finishUndo()
538 {
539         // Make sure the next operation will be stored.
540         d->undo_finished_ = true;
541 }
542
543
544 bool Undo::undoAction(CursorData & cur)
545 {
546         return d->undoRedoAction(cur, true);
547 }
548
549
550 bool Undo::redoAction(CursorData & cur)
551 {
552         return d->undoRedoAction(cur, false);
553 }
554
555
556 void Undo::beginUndoGroup()
557 {
558         if (d->group_level_ == 0) {
559                 // create a new group
560                 ++d->group_id_;
561                 LYXERR(Debug::UNDO, "+++++++ Creating new group " << d->group_id_
562                        << " for buffer " << &d->buffer_);
563         }
564         ++d->group_level_;
565 }
566
567
568 void Undo::beginUndoGroup(CursorData const & cur_before)
569 {
570         beginUndoGroup();
571         if (d->group_cur_before_.empty())
572                 d->group_cur_before_ = cur_before;
573 }
574
575
576 void Undo::endUndoGroup()
577 {
578         if (d->group_level_ == 0) {
579                 LYXERR0("There is no undo group to end here");
580                 return;
581         }
582         --d->group_level_;
583         if (d->group_level_ == 0) {
584                 // real end of the group
585                 d->group_cur_before_ = CursorData();
586                 LYXERR(Debug::UNDO, "------- End of group " << d->group_id_
587                        << " of buffer " << &d->buffer_);
588         }
589 }
590
591
592 void Undo::endUndoGroup(CursorData const & cur_after)
593 {
594         endUndoGroup();
595         if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
596                 d->undostack_.top().cur_after = cur_after;
597 }
598
599
600 void Undo::splitUndoGroup(CursorData const & cur)
601 {
602         size_t const level = d->group_level_;
603         d->group_level_ = 1;
604         endUndoGroup(cur);
605         beginUndoGroup(cur);
606         d->group_level_ = level;
607 }
608
609
610 bool Undo::activeUndoGroup() const
611 {
612         return d->group_level_ > 0
613                 && !d->undostack_.empty()
614                 && d->undostack_.top().group_id == d->group_id_;
615 }
616
617
618 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
619 {
620         d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
621 }
622
623
624 void Undo::recordUndo(CursorData const & cur, pit_type from, pit_type to)
625 {
626         d->recordUndo(ATOMIC_UNDO, cur, from, to, cur);
627 }
628
629
630 void Undo::recordUndoInset(CursorData const & cur, Inset const * inset)
631 {
632         if (!inset || inset == &cur.inset()) {
633                 DocIterator c = cur;
634                 c.pop_back();
635                 d->recordUndo(ATOMIC_UNDO, c, c.pit(), c.pit(), cur);
636         } else if (inset == cur.nextInset())
637                 recordUndo(cur);
638         else
639                 LYXERR0("Inset not found, no undo stack added.");
640 }
641
642
643 void Undo::recordUndoBufferParams(CursorData const & cur)
644 {
645         d->recordUndoBufferParams(cur);
646 }
647
648
649 void Undo::recordUndoFullBuffer(CursorData const & cur)
650 {
651         // This one may happen outside of the main undo group, so we
652         // put it in its own subgroup to avoid complaints.
653         beginUndoGroup();
654         d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
655                       0, d->buffer_.paragraphs().size() - 1, cur);
656         d->recordUndoBufferParams(cur);
657         endUndoGroup();
658 }
659
660 /// UndoGroupHelper class stuff
661
662 class UndoGroupHelper::Impl {
663         friend class UndoGroupHelper;
664         set<Buffer *> buffers_;
665 };
666
667
668 UndoGroupHelper::UndoGroupHelper(Buffer * buf) : d(new UndoGroupHelper::Impl)
669 {
670         resetBuffer(buf);
671 }
672
673
674 UndoGroupHelper::~UndoGroupHelper()
675 {
676         for (Buffer * buf : d->buffers_)
677                 if (theBufferList().isLoaded(buf) || theBufferList().isInternal(buf))
678                         buf->undo().endUndoGroup();
679         delete d;
680 }
681
682 void UndoGroupHelper::resetBuffer(Buffer * buf)
683 {
684         if (buf && d->buffers_.count(buf) == 0) {
685                 d->buffers_.insert(buf);
686                 buf->undo().beginUndoGroup();
687         }
688 }
689
690
691 } // namespace lyx