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