3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
10 * \author Jürgen Vigna
11 * \author Abdelrazak Younes
13 * Full author contact details are available in file CREDITS.
21 #include "BufferList.h"
22 #include "BufferParams.h"
24 #include "CutAndPaste.h"
25 #include "ErrorList.h"
26 #include "Paragraph.h"
27 #include "ParagraphList.h"
30 #include "mathed/MathData.h"
32 #include "insets/Inset.h"
33 #include "insets/InsetText.h"
35 #include "support/debug.h"
36 #include "support/lassert.h"
37 #include "support/lyxtime.h"
44 using namespace lyx::support;
50 These are the elements put on the undo stack. Each object contains
51 complete paragraphs from some cell and sufficient information to
52 restore the cursor state.
54 The cell is given by a DocIterator pointing to this cell, the
55 'interesting' range of paragraphs by counting them from begin and end
56 of cell, respectively.
58 The cursor is also given as DocIterator and should point to some place
59 in the stored paragraph range. In case of math, we simply store the
60 whole cell, as there usually is just a simple paragraph in a cell.
62 The idea is to store the contents of 'interesting' paragraphs in some
63 structure ('Undo') _before_ it is changed in some edit operation.
64 Obviously, the stored range should be as small as possible. However,
65 there is a lower limit: The StableDocIterator stored in the undo class
66 must be valid after the changes, too, as it will used as a pointer
67 where to insert the stored bits when performining undo.
72 UndoElement(UndoKind kin, CursorData const & cb,
73 StableDocIterator const & cel,
74 pit_type fro, pit_type en, ParagraphList * pl, MathData * ar,
75 bool lc, size_t gid) :
76 cur_before(cb), cell(cel), from(fro), end(en),
77 pars(pl), array(ar), bparams(nullptr),
78 group_id(gid), time(current_time()), kind(kin), lyx_clean(lc)
81 UndoElement(CursorData const & cb, BufferParams const & bp,
82 bool lc, size_t gid) :
83 cur_before(cb), cell(), from(0), end(0),
84 pars(nullptr), array(nullptr), bparams(new BufferParams(bp)),
85 group_id(gid), time(current_time()), kind(ATOMIC_UNDO), lyx_clean(lc)
88 UndoElement(UndoElement const & ue) :
89 cur_before(ue.cur_before), cur_after(ue.cur_after),
90 cell(ue.cell), from(ue.from), end(ue.end),
91 pars(ue.pars), array(ue.array),
92 bparams(ue.bparams ? new BufferParams(*ue.bparams) : nullptr),
93 group_id(ue.group_id), time(current_time()), kind(ue.kind),
94 lyx_clean(ue.lyx_clean)
102 /// the position of the cursor before recordUndo
103 CursorData cur_before;
104 /// the position of the cursor at the end of the undo group
105 CursorData cur_after;
106 /// the position of the cell described
107 StableDocIterator cell;
108 /// counted from begin of cell
110 /// complement to end of this cell
112 /// the contents of the saved Paragraphs (for texted)
113 ParagraphList * pars;
114 /// the contents of the saved MathData (for mathed)
116 /// Only used in case of params undo
117 BufferParams const * bparams;
118 /// the element's group id
122 /// Which kind of operation are we recording for?
124 /// Was the buffer clean at this point?
127 /// Protect construction
132 class UndoElementStack
135 /// limit is the maximum size of the stack
136 UndoElementStack(size_t limit = 100) { limit_ = limit; }
137 /// limit is the maximum size of the stack
138 ~UndoElementStack() { clear(); }
140 /// Return the top element.
141 UndoElement & top() { return c_.front(); }
143 /// Pop and throw away the top element.
144 void pop() { c_.pop_front(); }
146 /// Return true if the stack is empty.
147 bool empty() const { return c_.empty(); }
149 /// Clear all elements, deleting them.
151 for (size_t i = 0; i != c_.size(); ++i) {
158 /// Push an item on to the stack, deleting the bottom group on
160 void push(UndoElement const & v) {
161 // Remove some entries if the limit has been reached.
162 // However, if the only group on the stack is the one
163 // we are currently populating, do nothing.
164 if (c_.size() >= limit_
165 && c_.front().group_id != v.group_id) {
166 // remove a whole group at once.
167 const size_t gid = c_.back().group_id;
168 while (!c_.empty() && c_.back().group_id == gid)
174 /// Mark all the elements of the stack as dirty
176 for (size_t i = 0; i != c_.size(); ++i)
177 c_[i].lyx_clean = false;
181 /// Internal contents.
182 std::deque<UndoElement> c_;
183 /// The maximum number elements stored.
190 Private(Buffer & buffer) : buffer_(buffer),
191 group_id_(0), group_level_(0), undo_finished_(true) {}
193 // Do one undo/redo step
194 void doUndoRedoAction(CursorData & cur, UndoElementStack & stack,
195 UndoElementStack & otherStack);
196 // Apply one undo/redo group. Returns false if no undo possible.
197 bool undoRedoAction(CursorData & cur, bool isUndoOperation);
200 void doRecordUndo(UndoKind kind,
201 DocIterator const & cell,
204 CursorData const & cur,
205 UndoElementStack & stack);
207 void recordUndo(UndoKind kind,
208 DocIterator const & cell,
211 CursorData const & cur);
213 void doRecordUndoBufferParams(CursorData const & cur, UndoElementStack & stack);
215 void recordUndoBufferParams(CursorData const & cur);
220 UndoElementStack undostack_;
222 UndoElementStack redostack_;
224 /// Current group Id.
226 /// Current group nesting nevel.
228 /// the position of cursor before the group was created
229 CursorData group_cur_before_;
231 /// The flag used by Undo::finishUndo().
237 /////////////////////////////////////////////////////////////////////
241 /////////////////////////////////////////////////////////////////////
244 Undo::Undo(Buffer & buffer)
245 : d(new Undo::Private(buffer))
257 d->undostack_.clear();
258 d->redostack_.clear();
259 d->undo_finished_ = true;
260 // We used to do that, but I believe it is better to keep
261 // groups (only used in Buffer::reload for now (JMarc)
263 //d->group_level_ = 0;
267 bool Undo::hasUndoStack() const
269 return !d->undostack_.empty();
273 bool Undo::hasRedoStack() const
275 return !d->redostack_.empty();
279 void Undo::markDirty()
281 d->undo_finished_ = true;
282 d->undostack_.markDirty();
283 d->redostack_.markDirty();
287 /////////////////////////////////////////////////////////////////////
291 ///////////////////////////////////////////////////////////////////////
293 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
295 StableDocIterator tmpi2 = i2;
296 tmpi2.pos() = i1.pos();
301 void Undo::Private::doRecordUndo(UndoKind kind,
302 DocIterator const & cell,
303 pit_type first_pit, pit_type last_pit,
304 CursorData const & cur_before,
305 UndoElementStack & stack)
308 LYXERR0("There is no group open (creating one)");
312 if (first_pit > last_pit)
313 swap(first_pit, last_pit);
315 // Undo::ATOMIC are always recorded (no overlapping there).
316 // As nobody wants all removed character appear one by one when undoing,
317 // we want combine 'similar' non-ATOMIC undo recordings to one.
318 pit_type from = first_pit;
319 pit_type end = cell.lastpit() - last_pit;
321 && kind != ATOMIC_UNDO
323 && !stack.top().bparams
324 && samePar(stack.top().cell, cell)
325 && stack.top().kind == kind
326 && stack.top().from == from
327 && stack.top().end == end
328 && stack.top().cur_after == cur_before
329 && current_time() - stack.top().time <= 2) {
330 // reset cur_after; it will be filled correctly by endUndoGroup.
331 stack.top().cur_after = CursorData();
332 // update the timestamp of the undo element
333 stack.top().time = current_time();
337 LYXERR(Debug::UNDO, "Create undo element of group " << group_id_);
338 // create the position information of the Undo entry
339 UndoElement undo(kind,
340 group_cur_before_.empty() ? cur_before : group_cur_before_,
341 cell, from, end, nullptr, nullptr, buffer_.isClean(), group_id_);
343 // fill in the real data to be saved
344 if (cell.inMathed()) {
345 // simply use the whole cell
346 MathData & ar = cell.cell();
347 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
349 // some more effort needed here as 'the whole cell' of the
350 // main Text _is_ the whole document.
351 // record the relevant paragraphs
352 Text const * text = cell.text();
354 ParagraphList const & plist = text->paragraphs();
355 ParagraphList::const_iterator first = plist.begin();
356 advance(first, first_pit);
357 ParagraphList::const_iterator last = plist.begin();
358 advance(last, last_pit + 1);
359 undo.pars = new ParagraphList(first, last);
362 // push the undo entry to undo stack
364 //lyxerr << "undo record: " << stack.top() << endl;
368 void Undo::Private::recordUndo(UndoKind kind,
369 DocIterator const & cell,
370 pit_type first_pit, pit_type last_pit,
371 CursorData const & cur)
373 LASSERT(first_pit <= cell.lastpit(), return);
374 LASSERT(last_pit <= cell.lastpit(), return);
376 doRecordUndo(kind, cell, first_pit, last_pit, cur,
379 // next time we'll try again to combine entries if possible
380 undo_finished_ = false;
382 // If we ran recordUndo, it means that we plan to change the buffer
389 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
390 UndoElementStack & stack)
393 LYXERR0("There is no group open (creating one)");
397 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id_);
398 // create the position information of the Undo entry
399 UndoElement undo(group_cur_before_.empty() ? cur_before : group_cur_before_,
400 buffer_.params(), buffer_.isClean(),
403 // push the undo entry to undo stack
408 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
410 doRecordUndoBufferParams(cur, undostack_);
412 // next time we'll try again to combine entries if possible
413 undo_finished_ = false;
415 // If we ran recordUndo, it means that we plan to change the buffer
422 void Undo::Private::doUndoRedoAction(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
424 // Adjust undo stack and get hold of current undo data.
425 UndoElement & undo = stack.top();
426 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
427 // We'll pop the stack only when we're done with this element. So do NOT
428 // try to return early.
430 // We will store in otherstack the part of the document under 'undo'
431 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
434 doRecordUndoBufferParams(undo.cur_after, otherstack);
436 LATTEST(undo.end <= cell_dit.lastpit());
437 doRecordUndo(ATOMIC_UNDO, cell_dit,
438 undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
441 otherstack.top().cur_after = undo.cur_before;
443 // This does the actual undo/redo.
444 //LYXERR0("undo, performing: " << undo);
445 DocIterator dit = undo.cell.asDocIterator(&buffer_);
447 // This is a params undo element
448 delete otherstack.top().bparams;
449 otherstack.top().bparams = new BufferParams(buffer_.params());
450 DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
451 buffer_.params() = *undo.bparams;
452 // The error list is not supposed to be helpful here.
454 cap::switchBetweenClasses(olddc, buffer_.params().documentClassPtr(),
455 static_cast<InsetText &>(buffer_.inset()), el);
457 } else if (dit.inMathed()) {
458 // We stored the full cell here as there is not much to be
459 // gained by storing just 'a few' paragraphs (most if not
460 // all math inset cells have just one paragraph!)
461 //LYXERR0("undo.array: " << *undo.array);
463 dit.cell().swap(*undo.array);
464 dit.inset().setBuffer(buffer_);
466 undo.array = nullptr;
468 // Some finer machinery is needed here.
469 Text * text = dit.text();
472 ParagraphList & plist = text->paragraphs();
474 // remove new stuff between first and last
475 ParagraphList::iterator first = plist.begin();
476 advance(first, undo.from);
477 ParagraphList::iterator last = plist.begin();
478 advance(last, plist.size() - undo.end);
479 plist.erase(first, last);
481 // re-insert old stuff instead
482 first = plist.begin();
483 advance(first, undo.from);
485 // this ugly stuff is needed until we get rid of the
486 // inset_owner backpointer
487 ParagraphList::iterator pit = undo.pars->begin();
488 ParagraphList::iterator const end = undo.pars->end();
489 for (; pit != end; ++pit)
490 pit->setInsetOwner(dit.realInset());
491 plist.insert(first, undo.pars->begin(), undo.pars->end());
493 // set the buffers for insets we created
494 ParagraphList::iterator fpit = plist.begin();
495 advance(fpit, undo.from);
496 ParagraphList::iterator fend = fpit;
497 advance(fend, undo.pars->size());
498 for (; fpit != fend; ++fpit)
499 fpit->setInsetBuffers(buffer_);
505 // We'll clean up in release mode.
506 LASSERT(undo.pars == nullptr, undo.pars = nullptr);
507 LASSERT(undo.array == nullptr, undo.array = nullptr);
509 if (!undo.cur_before.empty())
510 cur = undo.cur_before;
515 // Now that we're done with undo, we pop it off the stack.
520 bool Undo::Private::undoRedoAction(CursorData & cur, bool isUndoOperation)
522 undo_finished_ = true;
524 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
530 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
532 const size_t gid = stack.top().group_id;
533 while (!stack.empty() && stack.top().group_id == gid)
534 doUndoRedoAction(cur, stack, otherstack);
539 void Undo::finishUndo()
541 // Make sure the next operation will be stored.
542 d->undo_finished_ = true;
546 bool Undo::undoAction(CursorData & cur)
548 return d->undoRedoAction(cur, true);
552 bool Undo::redoAction(CursorData & cur)
554 return d->undoRedoAction(cur, false);
558 void Undo::beginUndoGroup()
560 if (d->group_level_ == 0) {
561 // create a new group
563 LYXERR(Debug::UNDO, "+++++++ Creating new group " << d->group_id_
564 << " for buffer " << &d->buffer_);
570 void Undo::beginUndoGroup(CursorData const & cur_before)
573 if (d->group_cur_before_.empty())
574 d->group_cur_before_ = cur_before;
578 void Undo::endUndoGroup()
580 if (d->group_level_ == 0) {
581 LYXERR0("There is no undo group to end here");
585 if (d->group_level_ == 0) {
586 // real end of the group
587 d->group_cur_before_ = CursorData();
588 LYXERR(Debug::UNDO, "------- End of group " << d->group_id_
589 << " of buffer " << &d->buffer_);
594 void Undo::endUndoGroup(CursorData const & cur_after)
597 if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
598 d->undostack_.top().cur_after = cur_after;
602 void Undo::splitUndoGroup(CursorData const & cur)
604 size_t const level = d->group_level_;
608 d->group_level_ = level;
612 bool Undo::activeUndoGroup() const
614 return d->group_level_ > 0
615 && !d->undostack_.empty()
616 && d->undostack_.top().group_id == d->group_id_;
620 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
622 d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
626 void Undo::recordUndo(CursorData const & cur, pit_type from, pit_type to)
628 d->recordUndo(ATOMIC_UNDO, cur, from, to, cur);
632 void Undo::recordUndoInset(CursorData const & cur, Inset const * inset)
634 if (!inset || inset == &cur.inset()) {
637 d->recordUndo(ATOMIC_UNDO, c, c.pit(), c.pit(), cur);
638 } else if (inset == cur.nextInset())
641 LYXERR0("Inset not found, no undo stack added.");
645 void Undo::recordUndoBufferParams(CursorData const & cur)
647 d->recordUndoBufferParams(cur);
651 void Undo::recordUndoFullBuffer(CursorData const & cur)
653 // This one may happen outside of the main undo group, so we
654 // put it in its own subgroup to avoid complaints.
656 d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
657 0, d->buffer_.paragraphs().size() - 1, cur);
658 d->recordUndoBufferParams(cur);
662 /// UndoGroupHelper class stuff
664 class UndoGroupHelper::Impl {
665 friend class UndoGroupHelper;
666 set<Buffer *> buffers_;
670 UndoGroupHelper::UndoGroupHelper(Buffer * buf) : d(new UndoGroupHelper::Impl)
676 UndoGroupHelper::~UndoGroupHelper()
678 for (Buffer * buf : d->buffers_)
679 if (theBufferList().isLoaded(buf) || theBufferList().isInternal(buf))
680 buf->undo().endUndoGroup();
684 void UndoGroupHelper::resetBuffer(Buffer * buf)
686 if (buf && d->buffers_.count(buf) == 0) {
687 d->buffers_.insert(buf);
688 buf->undo().beginUndoGroup();