]> git.lyx.org Git - lyx.git/blob - src/Undo.cpp
Avoid row breaking at inconvenient places.
[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         if (buffer_.isReadonly())
378                 return;
379
380         doRecordUndo(kind, cell, first_pit, last_pit, cur,
381                 undostack_);
382
383         // next time we'll try again to combine entries if possible
384         undo_finished_ = false;
385
386         // If we ran recordUndo, it means that we plan to change the buffer
387         buffer_.markDirty();
388
389         redostack_.clear();
390 }
391
392
393 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
394                                              UndoElementStack & stack)
395 {
396         if (!group_level_) {
397                 LYXERR0("There is no group open (creating one)");
398                 ++group_id_;
399         }
400
401         LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id_);
402         // create the position information of the Undo entry
403         UndoElement undo(group_cur_before_.empty() ? cur_before : group_cur_before_,
404                      buffer_.params(), buffer_.isClean(),
405                                          group_id_);
406
407         // push the undo entry to undo stack
408         stack.push(undo);
409 }
410
411
412 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
413 {
414         if (buffer_.isReadonly())
415                 return;
416
417         doRecordUndoBufferParams(cur, undostack_);
418
419         // next time we'll try again to combine entries if possible
420         undo_finished_ = false;
421
422         // If we ran recordUndo, it means that we plan to change the buffer
423         buffer_.markDirty();
424
425         redostack_.clear();
426 }
427
428
429 void Undo::Private::doUndoRedoAction(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
430 {
431         // Adjust undo stack and get hold of current undo data.
432         UndoElement & undo = stack.top();
433         LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
434         // We'll pop the stack only when we're done with this element. So do NOT
435         // try to return early.
436
437         // We will store in otherstack the part of the document under 'undo'
438         DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
439
440         if (undo.bparams)
441                 doRecordUndoBufferParams(undo.cur_after, otherstack);
442         else {
443                 LATTEST(undo.end <= cell_dit.lastpit());
444                 doRecordUndo(ATOMIC_UNDO, cell_dit,
445                                          undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
446                                          otherstack);
447         }
448         otherstack.top().cur_after = undo.cur_before;
449
450         // This does the actual undo/redo.
451         //LYXERR0("undo, performing: " << undo);
452         DocIterator dit = undo.cell.asDocIterator(&buffer_);
453         if (undo.bparams) {
454                 // This is a params undo element
455                 delete otherstack.top().bparams;
456                 otherstack.top().bparams = new BufferParams(buffer_.params());
457                 DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
458                 buffer_.params() = *undo.bparams;
459                 cap::switchBetweenClasses(olddc, buffer_.params().documentClassPtr(),
460                         static_cast<InsetText &>(buffer_.inset()));
461         } else if (dit.inMathed()) {
462                 // We stored the full cell here as there is not much to be
463                 // gained by storing just 'a few' paragraphs (most if not
464                 // all math inset cells have just one paragraph!)
465                 //LYXERR0("undo.array: " << *undo.array);
466                 LBUFERR(undo.array);
467                 dit.cell().swap(*undo.array);
468                 dit.inset().setBuffer(buffer_);
469                 delete undo.array;
470                 undo.array = nullptr;
471         } else {
472                 // Some finer machinery is needed here.
473                 Text * text = dit.text();
474                 LBUFERR(text);
475                 LBUFERR(undo.pars);
476                 ParagraphList & plist = text->paragraphs();
477
478                 // remove new stuff between first and last
479                 ParagraphList::iterator first = plist.begin();
480                 advance(first, undo.from);
481                 ParagraphList::iterator last = plist.begin();
482                 advance(last, plist.size() - undo.end);
483                 plist.erase(first, last);
484
485                 // re-insert old stuff instead
486                 first = plist.begin();
487                 advance(first, undo.from);
488
489                 // this ugly stuff is needed until we get rid of the
490                 // inset_owner backpointer
491                 ParagraphList::iterator pit = undo.pars->begin();
492                 ParagraphList::iterator const end = undo.pars->end();
493                 for (; pit != end; ++pit)
494                         pit->setInsetOwner(dit.realInset());
495                 plist.insert(first, undo.pars->begin(), undo.pars->end());
496
497                 // set the buffers for insets we created
498                 ParagraphList::iterator fpit = plist.begin();
499                 advance(fpit, undo.from);
500                 ParagraphList::iterator fend = fpit;
501                 advance(fend, undo.pars->size());
502                 for (; fpit != fend; ++fpit)
503                         fpit->setInsetBuffers(buffer_);
504
505                 delete undo.pars;
506                 undo.pars = nullptr;
507         }
508
509         // We'll clean up in release mode.
510         LASSERT(undo.pars == nullptr, undo.pars = nullptr);
511         LASSERT(undo.array == nullptr, undo.array = nullptr);
512
513         if (!undo.cur_before.empty())
514                 cur = undo.cur_before;
515         if (undo.lyx_clean)
516                 buffer_.markClean();
517         else
518                 buffer_.markDirty();
519         // Now that we're done with undo, we pop it off the stack.
520         stack.pop();
521 }
522
523
524 bool Undo::Private::undoRedoAction(CursorData & cur, bool isUndoOperation)
525 {
526         if (buffer_.isReadonly())
527                 return false;
528
529         undo_finished_ = true;
530
531         UndoElementStack & stack = isUndoOperation ?  undostack_ : redostack_;
532
533         if (stack.empty())
534                 // Nothing to do.
535                 return false;
536
537         UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
538
539         const size_t gid = stack.top().group_id;
540         while (!stack.empty() && stack.top().group_id == gid)
541                 doUndoRedoAction(cur, stack, otherstack);
542         return true;
543 }
544
545
546 void Undo::finishUndo()
547 {
548         // Make sure the next operation will be stored.
549         d->undo_finished_ = true;
550 }
551
552
553 bool Undo::undoAction(CursorData & cur)
554 {
555         return d->undoRedoAction(cur, true);
556 }
557
558
559 bool Undo::redoAction(CursorData & cur)
560 {
561         return d->undoRedoAction(cur, false);
562 }
563
564
565 void Undo::beginUndoGroup()
566 {
567         if (d->group_level_ == 0) {
568                 // create a new group
569                 ++d->group_id_;
570                 LYXERR(Debug::UNDO, "+++++++ Creating new group " << d->group_id_
571                        << " for buffer " << &d->buffer_);
572         }
573         ++d->group_level_;
574 }
575
576
577 void Undo::beginUndoGroup(CursorData const & cur_before)
578 {
579         beginUndoGroup();
580         if (d->group_cur_before_.empty())
581                 d->group_cur_before_ = cur_before;
582 }
583
584
585 void Undo::endUndoGroup()
586 {
587         if (d->group_level_ == 0) {
588                 LYXERR0("There is no undo group to end here");
589                 return;
590         }
591         --d->group_level_;
592         if (d->group_level_ == 0) {
593                 // real end of the group
594                 d->group_cur_before_ = CursorData();
595                 LYXERR(Debug::UNDO, "------- End of group " << d->group_id_
596                        << " of buffer " << &d->buffer_);
597         }
598 }
599
600
601 void Undo::endUndoGroup(CursorData const & cur_after)
602 {
603         endUndoGroup();
604         if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
605                 d->undostack_.top().cur_after = cur_after;
606 }
607
608
609 void Undo::splitUndoGroup(CursorData const & cur)
610 {
611         size_t const level = d->group_level_;
612         d->group_level_ = 1;
613         endUndoGroup(cur);
614         beginUndoGroup(cur);
615         d->group_level_ = level;
616 }
617
618
619 bool Undo::activeUndoGroup() const
620 {
621         return d->group_level_ > 0
622                 && !d->undostack_.empty()
623                 && d->undostack_.top().group_id == d->group_id_;
624 }
625
626
627 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
628 {
629         d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
630 }
631
632
633 void Undo::recordUndo(CursorData const & cur, pit_type from, pit_type to)
634 {
635         d->recordUndo(ATOMIC_UNDO, cur, from, to, cur);
636 }
637
638
639 void Undo::recordUndoInset(CursorData const & cur, Inset const * inset)
640 {
641         if (!inset || inset == &cur.inset()) {
642                 DocIterator c = cur;
643                 c.pop_back();
644                 d->recordUndo(ATOMIC_UNDO, c, c.pit(), c.pit(), cur);
645         } else if (inset == cur.nextInset())
646                 recordUndo(cur);
647         else
648                 LYXERR0("Inset not found, no undo stack added.");
649 }
650
651
652 void Undo::recordUndoBufferParams(CursorData const & cur)
653 {
654         d->recordUndoBufferParams(cur);
655 }
656
657
658 void Undo::recordUndoFullBuffer(CursorData const & cur)
659 {
660         // This one may happen outside of the main undo group, so we
661         // put it in its own subgroup to avoid complaints.
662         beginUndoGroup();
663         d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
664                       0, d->buffer_.paragraphs().size() - 1, cur);
665         d->recordUndoBufferParams(cur);
666         endUndoGroup();
667 }
668
669 /// UndoGroupHelper class stuff
670
671 class UndoGroupHelper::Impl {
672         friend class UndoGroupHelper;
673         set<Buffer *> buffers_;
674 };
675
676
677 UndoGroupHelper::UndoGroupHelper(Buffer * buf) : d(new UndoGroupHelper::Impl)
678 {
679         resetBuffer(buf);
680 }
681
682
683 UndoGroupHelper::~UndoGroupHelper()
684 {
685         for (Buffer * buf : d->buffers_)
686                 if (theBufferList().isLoaded(buf) || theBufferList().isInternal(buf))
687                         buf->undo().endUndoGroup();
688         delete d;
689 }
690
691 void UndoGroupHelper::resetBuffer(Buffer * buf)
692 {
693         if (buf && d->buffers_.count(buf) == 0) {
694                 d->buffers_.insert(buf);
695                 buf->undo().beginUndoGroup();
696         }
697 }
698
699
700 } // namespace lyx