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"
23 #include "buffer_funcs.h"
25 #include "CutAndPaste.h"
26 #include "ErrorList.h"
27 #include "Paragraph.h"
28 #include "ParagraphList.h"
31 #include "mathed/MathSupport.h"
32 #include "mathed/MathData.h"
34 #include "insets/Inset.h"
35 #include "insets/InsetText.h"
37 #include "support/debug.h"
38 #include "support/gettext.h"
39 #include "support/lassert.h"
40 #include "support/lyxtime.h"
47 using namespace lyx::support;
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.
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.
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.
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.
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 cur_before(cb), cell(cel), from(fro), end(en),
80 pars(pl), array(ar), bparams(nullptr),
81 group_id(gid), time(current_time()), kind(kin), lyx_clean(lc)
84 UndoElement(CursorData const & cb, BufferParams const & bp,
85 bool lc, size_t gid) :
86 cur_before(cb), cell(), from(0), end(0),
87 pars(nullptr), array(nullptr), bparams(new BufferParams(bp)),
88 group_id(gid), time(current_time()), kind(ATOMIC_UNDO), lyx_clean(lc)
91 UndoElement(UndoElement const & ue) :
92 cur_before(ue.cur_before), cur_after(ue.cur_after),
93 cell(ue.cell), from(ue.from), end(ue.end),
94 pars(ue.pars), array(ue.array),
95 bparams(ue.bparams ? new BufferParams(*ue.bparams) : nullptr),
96 group_id(ue.group_id), time(current_time()), kind(ue.kind),
97 lyx_clean(ue.lyx_clean)
105 /// the position of the cursor before recordUndo
106 CursorData cur_before;
107 /// the position of the cursor at the end of the undo group
108 CursorData cur_after;
109 /// the position of the cell described
110 StableDocIterator cell;
111 /// counted from begin of cell
113 /// complement to end of this cell
115 /// the contents of the saved Paragraphs (for texted)
116 ParagraphList * pars;
117 /// the contents of the saved MathData (for mathed)
119 /// Only used in case of params undo
120 BufferParams const * bparams;
121 /// the element's group id
125 /// Which kind of operation are we recording for?
127 /// Was the buffer clean at this point?
130 /// Protect construction
135 class UndoElementStack
138 /// limit is the maximum size of the stack
139 UndoElementStack(size_t limit = 100) { limit_ = limit; }
140 /// limit is the maximum size of the stack
141 ~UndoElementStack() { clear(); }
143 /// Return the top element.
144 UndoElement & top() { return c_.front(); }
146 /// Pop and throw away the top element.
147 void pop() { c_.pop_front(); }
149 /// Return true if the stack is empty.
150 bool empty() const { return c_.empty(); }
152 /// Clear all elements, deleting them.
154 for (size_t i = 0; i != c_.size(); ++i) {
161 /// Push an item on to the stack, deleting the bottom group on
163 void push(UndoElement const & v) {
164 // Remove some entries if the limit has been reached.
165 // However, if the only group on the stack is the one
166 // we are currently populating, do nothing.
167 if (c_.size() >= limit_
168 && c_.front().group_id != v.group_id) {
169 // remove a whole group at once.
170 const size_t gid = c_.back().group_id;
171 while (!c_.empty() && c_.back().group_id == gid)
177 /// Mark all the elements of the stack as dirty
179 for (size_t i = 0; i != c_.size(); ++i)
180 c_[i].lyx_clean = false;
184 /// Internal contents.
185 std::deque<UndoElement> c_;
186 /// The maximum number elements stored.
193 Private(Buffer & buffer) : buffer_(buffer),
194 group_id_(0), group_level_(0), undo_finished_(true) {}
196 // Do one undo/redo step
197 void doUndoRedoAction(CursorData & cur, UndoElementStack & stack,
198 UndoElementStack & otherStack);
199 // Apply one undo/redo group. Returns false if no undo possible.
200 bool undoRedoAction(CursorData & cur, bool isUndoOperation);
203 void doRecordUndo(UndoKind kind,
204 DocIterator const & cell,
207 CursorData const & cur,
208 UndoElementStack & stack);
210 void recordUndo(UndoKind kind,
211 DocIterator const & cell,
214 CursorData const & cur);
216 void doRecordUndoBufferParams(CursorData const & cur, UndoElementStack & stack);
218 void recordUndoBufferParams(CursorData const & cur);
223 UndoElementStack undostack_;
225 UndoElementStack redostack_;
227 /// Current group Id.
229 /// Current group nesting nevel.
231 /// the position of cursor before the group was created
232 CursorData group_cur_before_;
234 /// The flag used by Undo::finishUndo().
240 /////////////////////////////////////////////////////////////////////
244 /////////////////////////////////////////////////////////////////////
247 Undo::Undo(Buffer & buffer)
248 : d(new Undo::Private(buffer))
260 d->undostack_.clear();
261 d->redostack_.clear();
262 d->undo_finished_ = true;
263 // We used to do that, but I believe it is better to keep
264 // groups (only used in Buffer::reload for now (JMarc)
266 //d->group_level_ = 0;
270 bool Undo::hasUndoStack() const
272 return !d->undostack_.empty();
276 bool Undo::hasRedoStack() const
278 return !d->redostack_.empty();
282 void Undo::markDirty()
284 d->undo_finished_ = true;
285 d->undostack_.markDirty();
286 d->redostack_.markDirty();
290 /////////////////////////////////////////////////////////////////////
294 ///////////////////////////////////////////////////////////////////////
296 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
298 StableDocIterator tmpi2 = i2;
299 tmpi2.pos() = i1.pos();
304 void Undo::Private::doRecordUndo(UndoKind kind,
305 DocIterator const & cell,
306 pit_type first_pit, pit_type last_pit,
307 CursorData const & cur_before,
308 UndoElementStack & stack)
311 LYXERR0("There is no group open (creating one)");
315 if (first_pit > last_pit)
316 swap(first_pit, last_pit);
318 // Undo::ATOMIC are always recorded (no overlapping there).
319 // As nobody wants all removed character appear one by one when undoing,
320 // we want combine 'similar' non-ATOMIC undo recordings to one.
321 pit_type from = first_pit;
322 pit_type end = cell.lastpit() - last_pit;
324 && kind != ATOMIC_UNDO
326 && !stack.top().bparams
327 && samePar(stack.top().cell, cell)
328 && stack.top().kind == kind
329 && stack.top().from == from
330 && stack.top().end == end
331 && stack.top().cur_after == cur_before
332 && current_time() - stack.top().time <= 2) {
333 // reset cur_after; it will be filled correctly by endUndoGroup.
334 stack.top().cur_after = CursorData();
335 // update the timestamp of the undo element
336 stack.top().time = current_time();
340 LYXERR(Debug::UNDO, "Create undo element of group " << group_id_);
341 // create the position information of the Undo entry
342 UndoElement undo(kind,
343 group_cur_before_.empty() ? cur_before : group_cur_before_,
344 cell, from, end, nullptr, nullptr, buffer_.isClean(), group_id_);
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());
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();
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);
365 // push the undo entry to undo stack
367 //lyxerr << "undo record: " << stack.top() << endl;
371 void Undo::Private::recordUndo(UndoKind kind,
372 DocIterator const & cell,
373 pit_type first_pit, pit_type last_pit,
374 CursorData const & cur)
376 LASSERT(first_pit <= cell.lastpit(), return);
377 LASSERT(last_pit <= cell.lastpit(), return);
379 doRecordUndo(kind, cell, first_pit, last_pit, cur,
382 // next time we'll try again to combine entries if possible
383 undo_finished_ = false;
385 // If we ran recordUndo, it means that we plan to change the buffer
392 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
393 UndoElementStack & stack)
396 LYXERR0("There is no group open (creating one)");
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(group_cur_before_.empty() ? cur_before : group_cur_before_,
403 buffer_.params(), buffer_.isClean(),
406 // push the undo entry to undo stack
411 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
413 doRecordUndoBufferParams(cur, undostack_);
415 // next time we'll try again to combine entries if possible
416 undo_finished_ = false;
418 // If we ran recordUndo, it means that we plan to change the buffer
425 void Undo::Private::doUndoRedoAction(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
427 // Adjust undo stack and get hold of current undo data.
428 UndoElement & undo = stack.top();
429 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
430 // We'll pop the stack only when we're done with this element. So do NOT
431 // try to return early.
433 // We will store in otherstack the part of the document under 'undo'
434 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
437 doRecordUndoBufferParams(undo.cur_after, otherstack);
439 LATTEST(undo.end <= cell_dit.lastpit());
440 doRecordUndo(ATOMIC_UNDO, cell_dit,
441 undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
444 otherstack.top().cur_after = undo.cur_before;
446 // This does the actual undo/redo.
447 //LYXERR0("undo, performing: " << undo);
448 DocIterator dit = undo.cell.asDocIterator(&buffer_);
450 // This is a params undo element
451 delete otherstack.top().bparams;
452 otherstack.top().bparams = new BufferParams(buffer_.params());
453 DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
454 buffer_.params() = *undo.bparams;
455 // The error list is not supposed to be helpful here.
457 cap::switchBetweenClasses(olddc, buffer_.params().documentClassPtr(),
458 static_cast<InsetText &>(buffer_.inset()), el);
460 } else if (dit.inMathed()) {
461 // We stored the full cell here as there is not much to be
462 // gained by storing just 'a few' paragraphs (most if not
463 // all math inset cells have just one paragraph!)
464 //LYXERR0("undo.array: " << *undo.array);
466 dit.cell().swap(*undo.array);
467 dit.inset().setBuffer(buffer_);
469 undo.array = nullptr;
471 // Some finer machinery is needed here.
472 Text * text = dit.text();
475 ParagraphList & plist = text->paragraphs();
477 // remove new stuff between first and last
478 ParagraphList::iterator first = plist.begin();
479 advance(first, undo.from);
480 ParagraphList::iterator last = plist.begin();
481 advance(last, plist.size() - undo.end);
482 plist.erase(first, last);
484 // re-insert old stuff instead
485 first = plist.begin();
486 advance(first, undo.from);
488 // this ugly stuff is needed until we get rid of the
489 // inset_owner backpointer
490 ParagraphList::iterator pit = undo.pars->begin();
491 ParagraphList::iterator const end = undo.pars->end();
492 for (; pit != end; ++pit)
493 pit->setInsetOwner(dit.realInset());
494 plist.insert(first, undo.pars->begin(), undo.pars->end());
496 // set the buffers for insets we created
497 ParagraphList::iterator fpit = plist.begin();
498 advance(fpit, undo.from);
499 ParagraphList::iterator fend = fpit;
500 advance(fend, undo.pars->size());
501 for (; fpit != fend; ++fpit)
502 fpit->setInsetBuffers(buffer_);
508 // We'll clean up in release mode.
509 LASSERT(undo.pars == nullptr, undo.pars = nullptr);
510 LASSERT(undo.array == nullptr, undo.array = nullptr);
512 if (!undo.cur_before.empty())
513 cur = undo.cur_before;
518 // Now that we're done with undo, we pop it off the stack.
523 bool Undo::Private::undoRedoAction(CursorData & cur, bool isUndoOperation)
525 undo_finished_ = true;
527 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
533 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
535 const size_t gid = stack.top().group_id;
536 while (!stack.empty() && stack.top().group_id == gid)
537 doUndoRedoAction(cur, stack, otherstack);
542 void Undo::finishUndo()
544 // Make sure the next operation will be stored.
545 d->undo_finished_ = true;
549 bool Undo::undoAction(CursorData & cur)
551 return d->undoRedoAction(cur, true);
555 bool Undo::redoAction(CursorData & cur)
557 return d->undoRedoAction(cur, false);
561 void Undo::beginUndoGroup()
563 if (d->group_level_ == 0) {
564 // create a new group
566 LYXERR(Debug::UNDO, "+++++++ Creating new group " << d->group_id_
567 << " for buffer " << &d->buffer_);
573 void Undo::beginUndoGroup(CursorData const & cur_before)
576 if (d->group_cur_before_.empty())
577 d->group_cur_before_ = cur_before;
581 void Undo::endUndoGroup()
583 if (d->group_level_ == 0) {
584 LYXERR0("There is no undo group to end here");
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_);
597 void Undo::endUndoGroup(CursorData const & cur_after)
600 if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
601 d->undostack_.top().cur_after = cur_after;
605 void Undo::splitUndoGroup(CursorData const & cur)
607 size_t const level = d->group_level_;
611 d->group_level_ = level;
615 bool Undo::activeUndoGroup() const
617 return d->group_level_ > 0
618 && !d->undostack_.empty()
619 && d->undostack_.top().group_id == d->group_id_;
623 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
625 d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
629 void Undo::recordUndo(CursorData const & cur, pit_type from, pit_type to)
631 d->recordUndo(ATOMIC_UNDO, cur, from, to, cur);
635 void Undo::recordUndoInset(CursorData const & cur, Inset const * inset)
637 if (!inset || inset == &cur.inset()) {
640 d->recordUndo(ATOMIC_UNDO, c, c.pit(), c.pit(), cur);
641 } else if (inset == cur.nextInset())
644 LYXERR0("Inset not found, no undo stack added.");
648 void Undo::recordUndoBufferParams(CursorData const & cur)
650 d->recordUndoBufferParams(cur);
654 void Undo::recordUndoFullBuffer(CursorData const & cur)
656 // This one may happen outside of the main undo group, so we
657 // put it in its own subgroup to avoid complaints.
659 d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
660 0, d->buffer_.paragraphs().size() - 1, cur);
661 d->recordUndoBufferParams(cur);
665 /// UndoGroupHelper class stuff
667 class UndoGroupHelper::Impl {
668 friend class UndoGroupHelper;
669 set<Buffer *> buffers_;
673 UndoGroupHelper::UndoGroupHelper(Buffer * buf) : d(new UndoGroupHelper::Impl)
679 UndoGroupHelper::~UndoGroupHelper()
681 for (Buffer * buf : d->buffers_)
682 if (theBufferList().isLoaded(buf) || theBufferList().isInternal(buf))
683 buf->undo().endUndoGroup();
687 void UndoGroupHelper::resetBuffer(Buffer * buf)
689 if (buf && d->buffers_.count(buf) == 0) {
690 d->buffers_.insert(buf);
691 buf->undo().beginUndoGroup();