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