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/InsetMath.h"
31 #include "mathed/MathData.h"
32 #include "mathed/MathRow.h"
34 #include "insets/InsetText.h"
36 #include "support/debug.h"
37 #include "support/lassert.h"
38 #include "support/lyxtime.h"
45 using namespace lyx::support;
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.
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.
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.
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.
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)
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)
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)
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
111 /// complement to end of this cell
113 /// the contents of the saved Paragraphs (for texted)
114 ParagraphList * pars;
115 /// the contents of the saved MathData (for mathed)
117 /// Only used in case of params undo
118 BufferParams const * bparams;
119 /// the element's group id
123 /// Which kind of operation are we recording for?
125 /// Was the buffer clean at this point?
128 /// Protect construction
133 class UndoElementStack
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(); }
141 /// Return the top element.
142 UndoElement & top() { return c_.front(); }
144 /// Pop and throw away the top element.
145 void pop() { c_.pop_front(); }
147 /// Return true if the stack is empty.
148 bool empty() const { return c_.empty(); }
150 /// Clear all elements, deleting them.
152 for (size_t i = 0; i != c_.size(); ++i) {
159 /// Push an item on to the stack, deleting the bottom group on
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)
175 /// Mark all the elements of the stack as dirty
177 for (size_t i = 0; i != c_.size(); ++i)
178 c_[i].lyx_clean = false;
182 /// Internal contents.
183 std::deque<UndoElement> c_;
184 /// The maximum number elements stored.
191 Private(Buffer & buffer) : buffer_(buffer),
192 group_id_(0), group_level_(0), undo_finished_(true) {}
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);
201 void doRecordUndo(UndoKind kind,
202 DocIterator const & cell,
205 CursorData const & cur,
206 UndoElementStack & stack);
208 void recordUndo(UndoKind kind,
209 DocIterator const & cell,
212 CursorData const & cur);
214 void doRecordUndoBufferParams(CursorData const & cur, UndoElementStack & stack);
216 void recordUndoBufferParams(CursorData const & cur);
221 UndoElementStack undostack_;
223 UndoElementStack redostack_;
225 /// Current group Id.
227 /// Current group nesting nevel.
229 /// the position of cursor before the group was created
230 CursorData group_cur_before_;
232 /// The flag used by Undo::finishUndo().
238 /////////////////////////////////////////////////////////////////////
242 /////////////////////////////////////////////////////////////////////
245 Undo::Undo(Buffer & buffer)
246 : d(new Undo::Private(buffer))
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)
264 //d->group_level_ = 0;
268 bool Undo::hasUndoStack() const
270 return !d->undostack_.empty();
274 bool Undo::hasRedoStack() const
276 return !d->redostack_.empty();
280 void Undo::markDirty()
282 d->undo_finished_ = true;
283 d->undostack_.markDirty();
284 d->redostack_.markDirty();
288 /////////////////////////////////////////////////////////////////////
292 ///////////////////////////////////////////////////////////////////////
294 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
296 StableDocIterator tmpi2 = i2;
297 tmpi2.pos() = i1.pos();
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)
309 LYXERR0("There is no group open (creating one)");
313 if (first_pit > last_pit)
314 swap(first_pit, last_pit);
315 pit_type const from = first_pit;
316 pit_type const end = cell.lastpit() - last_pit;
318 /* Undo coalescing: if the undo element we want to add only
319 * changes stuff that was already modified by the previous one on
320 * undo stack (in the same group), then skip it. There is nothing
321 * to gain in adding it to the stack. The code below works for
322 * both texted and mathed.
325 && stack.top().group_id == group_id_
326 && !stack.top().bparams
327 && samePar(stack.top().cell, cell)
328 //&& stack.top().kind == kind // needed?
329 && stack.top().from <= from
330 && stack.top().end >= end) {
331 LYXERR(Debug::UNDO, "Undo coalescing: skip entry");
335 // Undo::ATOMIC are always recorded (no overlapping there).
336 // As nobody wants all removed character appear one by one when undoing,
337 // we want combine 'similar' non-ATOMIC undo recordings to one.
339 && kind != ATOMIC_UNDO
341 && !stack.top().bparams
342 && samePar(stack.top().cell, cell)
343 && stack.top().kind == kind
344 && stack.top().from == from
345 && stack.top().end == end
346 && stack.top().cur_after == cur_before
347 && current_time() - stack.top().time <= 2) {
348 // reset cur_after; it will be filled correctly by endUndoGroup.
349 stack.top().cur_after = CursorData();
350 // update the timestamp of the undo element
351 stack.top().time = current_time();
355 LYXERR(Debug::UNDO, "Create undo element of group " << group_id_);
356 // create the position information of the Undo entry
357 UndoElement undo(kind,
358 group_cur_before_.empty() ? cur_before : group_cur_before_,
359 cell, from, end, nullptr, nullptr, buffer_.isClean(), group_id_);
361 // fill in the real data to be saved
362 if (cell.inMathed()) {
363 // simply use the whole cell
364 MathData & ar = cell.cell();
365 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
367 // some more effort needed here as 'the whole cell' of the
368 // main Text _is_ the whole document.
369 // record the relevant paragraphs
370 Text const * text = cell.text();
372 ParagraphList const & plist = text->paragraphs();
373 ParagraphList::const_iterator first = plist.begin();
374 advance(first, first_pit);
375 ParagraphList::const_iterator last = plist.begin();
376 advance(last, last_pit + 1);
377 undo.pars = new ParagraphList(first, last);
380 // push the undo entry to undo stack
382 //lyxerr << "undo record: " << stack.top() << endl;
386 void Undo::Private::recordUndo(UndoKind kind,
387 DocIterator const & cell,
388 pit_type first_pit, pit_type last_pit,
389 CursorData const & cur)
391 LASSERT(first_pit <= cell.lastpit(), return);
392 LASSERT(last_pit <= cell.lastpit(), return);
394 if (buffer_.isReadonly())
397 doRecordUndo(kind, cell, first_pit, last_pit, cur,
400 // next time we'll try again to combine entries if possible
401 undo_finished_ = false;
403 // If we ran recordUndo, it means that we plan to change the buffer
410 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
411 UndoElementStack & stack)
414 LYXERR0("There is no group open (creating one)");
418 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id_);
419 // create the position information of the Undo entry
420 UndoElement undo(group_cur_before_.empty() ? cur_before : group_cur_before_,
421 buffer_.params(), buffer_.isClean(),
424 // push the undo entry to undo stack
429 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
431 if (buffer_.isReadonly())
434 doRecordUndoBufferParams(cur, undostack_);
436 // next time we'll try again to combine entries if possible
437 undo_finished_ = false;
439 // If we ran recordUndo, it means that we plan to change the buffer
446 void Undo::Private::doUndoRedoAction(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
448 // Adjust undo stack and get hold of current undo data.
449 UndoElement & undo = stack.top();
450 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
451 // We'll pop the stack only when we're done with this element. So do NOT
452 // try to return early.
454 // We will store in otherstack the part of the document under 'undo'
455 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
458 doRecordUndoBufferParams(undo.cur_after, otherstack);
460 LATTEST(undo.end <= cell_dit.lastpit());
461 doRecordUndo(ATOMIC_UNDO, cell_dit,
462 undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
465 otherstack.top().cur_after = undo.cur_before;
467 // This does the actual undo/redo.
468 //LYXERR0("undo, performing: " << undo);
469 DocIterator dit = undo.cell.asDocIterator(&buffer_);
471 // This is a params undo element
472 delete otherstack.top().bparams;
473 otherstack.top().bparams = new BufferParams(buffer_.params());
474 DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
475 buffer_.params() = *undo.bparams;
476 cap::switchBetweenClasses(olddc, buffer_.params().documentClassPtr(),
477 static_cast<InsetText &>(buffer_.inset()));
478 } else if (dit.inMathed()) {
479 // We stored the full cell here as there is not much to be
480 // gained by storing just 'a few' paragraphs (most if not
481 // all math inset cells have just one paragraph!)
482 //LYXERR0("undo.array: " << *undo.array);
484 dit.cell().swap(*undo.array);
485 dit.inset().setBuffer(buffer_);
487 undo.array = nullptr;
489 // Some finer machinery is needed here.
490 Text * text = dit.text();
493 ParagraphList & plist = text->paragraphs();
495 // remove new stuff between first and last
496 ParagraphList::iterator first = plist.begin();
497 advance(first, undo.from);
498 ParagraphList::iterator last = plist.begin();
499 advance(last, plist.size() - undo.end);
500 plist.erase(first, last);
502 // re-insert old stuff instead
503 first = plist.begin();
504 advance(first, undo.from);
506 // this ugly stuff is needed until we get rid of the
507 // inset_owner backpointer
508 ParagraphList::iterator pit = undo.pars->begin();
509 ParagraphList::iterator const end = undo.pars->end();
510 for (; pit != end; ++pit)
511 pit->setInsetOwner(dit.realInset());
512 plist.insert(first, undo.pars->begin(), undo.pars->end());
514 // set the buffers for insets we created
515 ParagraphList::iterator fpit = plist.begin();
516 advance(fpit, undo.from);
517 ParagraphList::iterator fend = fpit;
518 advance(fend, undo.pars->size());
519 for (; fpit != fend; ++fpit)
520 fpit->setInsetBuffers(buffer_);
526 // We'll clean up in release mode.
527 LASSERT(undo.pars == nullptr, undo.pars = nullptr);
528 LASSERT(undo.array == nullptr, undo.array = nullptr);
530 if (!undo.cur_before.empty())
531 cur = undo.cur_before;
536 // Now that we're done with undo, we pop it off the stack.
541 bool Undo::Private::undoRedoAction(CursorData & cur, bool isUndoOperation)
543 if (buffer_.isReadonly())
546 undo_finished_ = true;
548 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
554 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
556 const size_t gid = stack.top().group_id;
557 while (!stack.empty() && stack.top().group_id == gid)
558 doUndoRedoAction(cur, stack, otherstack);
563 void Undo::finishUndo()
565 // Make sure the next operation will be stored.
566 d->undo_finished_ = true;
570 bool Undo::undoAction(CursorData & cur)
572 return d->undoRedoAction(cur, true);
576 bool Undo::redoAction(CursorData & cur)
578 return d->undoRedoAction(cur, false);
582 void Undo::beginUndoGroup()
584 if (d->group_level_ == 0) {
585 // create a new group
587 LYXERR(Debug::UNDO, "+++++++ Creating new group " << d->group_id_
588 << " for buffer " << &d->buffer_);
594 void Undo::beginUndoGroup(CursorData const & cur_before)
597 if (d->group_cur_before_.empty())
598 d->group_cur_before_ = cur_before;
602 void Undo::endUndoGroup()
604 if (d->group_level_ == 0) {
605 LYXERR0("There is no undo group to end here");
609 if (d->group_level_ == 0) {
610 // real end of the group
611 d->group_cur_before_ = CursorData();
612 LYXERR(Debug::UNDO, "------- End of group " << d->group_id_
613 << " of buffer " << &d->buffer_);
618 void Undo::endUndoGroup(CursorData const & cur_after)
621 if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
622 d->undostack_.top().cur_after = cur_after;
626 void Undo::splitUndoGroup(CursorData const & cur)
628 size_t const level = d->group_level_;
632 d->group_level_ = level;
636 bool Undo::activeUndoGroup() const
638 return d->group_level_ > 0
639 && !d->undostack_.empty()
640 && d->undostack_.top().group_id == d->group_id_;
644 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
646 d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
650 void Undo::recordUndo(CursorData const & cur, pit_type from, pit_type to)
652 d->recordUndo(ATOMIC_UNDO, cur, from, to, cur);
656 void Undo::recordUndoInset(CursorData const & cur, Inset const * inset)
658 if (!inset || inset == &cur.inset()) {
661 d->recordUndo(ATOMIC_UNDO, c, c.pit(), c.pit(), cur);
662 } else if (inset == cur.nextInset())
665 LYXERR0("Inset not found, no undo stack added.");
669 void Undo::recordUndoBufferParams(CursorData const & cur)
671 d->recordUndoBufferParams(cur);
675 void Undo::recordUndoFullBuffer(CursorData const & cur)
677 // This one may happen outside of the main undo group, so we
678 // put it in its own subgroup to avoid complaints.
680 d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
681 0, d->buffer_.paragraphs().size() - 1, cur);
682 d->recordUndoBufferParams(cur);
686 /// UndoGroupHelper class stuff
688 class UndoGroupHelper::Impl {
689 friend class UndoGroupHelper;
690 set<Buffer *> buffers_;
694 UndoGroupHelper::UndoGroupHelper(Buffer * buf) : d(new UndoGroupHelper::Impl)
700 UndoGroupHelper::~UndoGroupHelper()
702 for (Buffer * buf : d->buffers_)
703 if (theBufferList().isLoaded(buf) || theBufferList().isInternal(buf))
704 buf->undo().endUndoGroup();
708 void UndoGroupHelper::resetBuffer(Buffer * buf)
710 if (buf && d->buffers_.count(buf) == 0) {
711 d->buffers_.insert(buf);
712 buf->undo().beginUndoGroup();