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