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/InsetText.h"
34 #include "support/debug.h"
35 #include "support/lassert.h"
36 #include "support/lyxtime.h"
43 using namespace lyx::support;
49 These are the elements put on the undo stack. Each object contains
50 complete paragraphs from some cell and sufficient information to
51 restore the cursor state.
53 The cell is given by a DocIterator pointing to this cell, the
54 'interesting' range of paragraphs by counting them from begin and end
55 of cell, respectively.
57 The cursor is also given as DocIterator and should point to some place
58 in the stored paragraph range. In case of math, we simply store the
59 whole cell, as there usually is just a simple paragraph in a cell.
61 The idea is to store the contents of 'interesting' paragraphs in some
62 structure ('Undo') _before_ it is changed in some edit operation.
63 Obviously, the stored range should be as small as possible. However,
64 there is a lower limit: The StableDocIterator stored in the undo class
65 must be valid after the changes, too, as it will used as a pointer
66 where to insert the stored bits when performining undo.
71 UndoElement(UndoKind kin, CursorData const & cb,
72 StableDocIterator const & cel,
73 pit_type fro, pit_type en, ParagraphList * pl, MathData * ar,
74 bool lc, size_t gid) :
75 cur_before(cb), cell(cel), from(fro), end(en),
76 pars(pl), array(ar), bparams(nullptr),
77 group_id(gid), time(current_time()), kind(kin), lyx_clean(lc)
80 UndoElement(CursorData const & cb, BufferParams const & bp,
81 bool lc, size_t gid) :
82 cur_before(cb), cell(), from(0), end(0),
83 pars(nullptr), array(nullptr), bparams(new BufferParams(bp)),
84 group_id(gid), time(current_time()), kind(ATOMIC_UNDO), lyx_clean(lc)
87 UndoElement(UndoElement const & ue) :
88 cur_before(ue.cur_before), cur_after(ue.cur_after),
89 cell(ue.cell), from(ue.from), end(ue.end),
90 pars(ue.pars), array(ue.array),
91 bparams(ue.bparams ? new BufferParams(*ue.bparams) : nullptr),
92 group_id(ue.group_id), time(current_time()), kind(ue.kind),
93 lyx_clean(ue.lyx_clean)
101 /// the position of the cursor before recordUndo
102 CursorData cur_before;
103 /// the position of the cursor at the end of the undo group
104 CursorData cur_after;
105 /// the position of the cell described
106 StableDocIterator cell;
107 /// counted from begin of cell
109 /// complement to end of this cell
111 /// the contents of the saved Paragraphs (for texted)
112 ParagraphList * pars;
113 /// the contents of the saved MathData (for mathed)
115 /// Only used in case of params undo
116 BufferParams const * bparams;
117 /// the element's group id
121 /// Which kind of operation are we recording for?
123 /// Was the buffer clean at this point?
126 /// Protect construction
131 class UndoElementStack
134 /// limit is the maximum size of the stack
135 UndoElementStack(size_t limit = 100) { limit_ = limit; }
136 /// limit is the maximum size of the stack
137 ~UndoElementStack() { clear(); }
139 /// Return the top element.
140 UndoElement & top() { return c_.front(); }
142 /// Pop and throw away the top element.
143 void pop() { c_.pop_front(); }
145 /// Return true if the stack is empty.
146 bool empty() const { return c_.empty(); }
148 /// Clear all elements, deleting them.
150 for (size_t i = 0; i != c_.size(); ++i) {
157 /// Push an item on to the stack, deleting the bottom group on
159 void push(UndoElement const & v) {
160 // Remove some entries if the limit has been reached.
161 // However, if the only group on the stack is the one
162 // we are currently populating, do nothing.
163 if (c_.size() >= limit_
164 && c_.front().group_id != v.group_id) {
165 // remove a whole group at once.
166 const size_t gid = c_.back().group_id;
167 while (!c_.empty() && c_.back().group_id == gid)
173 /// Mark all the elements of the stack as dirty
175 for (size_t i = 0; i != c_.size(); ++i)
176 c_[i].lyx_clean = false;
180 /// Internal contents.
181 std::deque<UndoElement> c_;
182 /// The maximum number elements stored.
189 Private(Buffer & buffer) : buffer_(buffer),
190 group_id_(0), group_level_(0), undo_finished_(true) {}
192 // Do one undo/redo step
193 void doUndoRedoAction(CursorData & cur, UndoElementStack & stack,
194 UndoElementStack & otherStack);
195 // Apply one undo/redo group. Returns false if no undo possible.
196 bool undoRedoAction(CursorData & cur, bool isUndoOperation);
199 void doRecordUndo(UndoKind kind,
200 DocIterator const & cell,
203 CursorData const & cur,
204 UndoElementStack & stack);
206 void recordUndo(UndoKind kind,
207 DocIterator const & cell,
210 CursorData const & cur);
212 void doRecordUndoBufferParams(CursorData const & cur, UndoElementStack & stack);
214 void recordUndoBufferParams(CursorData const & cur);
219 UndoElementStack undostack_;
221 UndoElementStack redostack_;
223 /// Current group Id.
225 /// Current group nesting nevel.
227 /// the position of cursor before the group was created
228 CursorData group_cur_before_;
230 /// The flag used by Undo::finishUndo().
236 /////////////////////////////////////////////////////////////////////
240 /////////////////////////////////////////////////////////////////////
243 Undo::Undo(Buffer & buffer)
244 : d(new Undo::Private(buffer))
256 d->undostack_.clear();
257 d->redostack_.clear();
258 d->undo_finished_ = true;
259 // We used to do that, but I believe it is better to keep
260 // groups (only used in Buffer::reload for now (JMarc)
262 //d->group_level_ = 0;
266 bool Undo::hasUndoStack() const
268 return !d->undostack_.empty();
272 bool Undo::hasRedoStack() const
274 return !d->redostack_.empty();
278 void Undo::markDirty()
280 d->undo_finished_ = true;
281 d->undostack_.markDirty();
282 d->redostack_.markDirty();
286 /////////////////////////////////////////////////////////////////////
290 ///////////////////////////////////////////////////////////////////////
292 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
294 StableDocIterator tmpi2 = i2;
295 tmpi2.pos() = i1.pos();
300 void Undo::Private::doRecordUndo(UndoKind kind,
301 DocIterator const & cell,
302 pit_type first_pit, pit_type last_pit,
303 CursorData const & cur_before,
304 UndoElementStack & stack)
307 LYXERR0("There is no group open (creating one)");
311 if (first_pit > last_pit)
312 swap(first_pit, last_pit);
314 // Undo::ATOMIC are always recorded (no overlapping there).
315 // As nobody wants all removed character appear one by one when undoing,
316 // we want combine 'similar' non-ATOMIC undo recordings to one.
317 pit_type from = first_pit;
318 pit_type end = cell.lastpit() - last_pit;
320 && kind != ATOMIC_UNDO
322 && !stack.top().bparams
323 && samePar(stack.top().cell, cell)
324 && stack.top().kind == kind
325 && stack.top().from == from
326 && stack.top().end == end
327 && stack.top().cur_after == cur_before
328 && current_time() - stack.top().time <= 2) {
329 // reset cur_after; it will be filled correctly by endUndoGroup.
330 stack.top().cur_after = CursorData();
331 // update the timestamp of the undo element
332 stack.top().time = current_time();
336 LYXERR(Debug::UNDO, "Create undo element of group " << group_id_);
337 // create the position information of the Undo entry
338 UndoElement undo(kind,
339 group_cur_before_.empty() ? cur_before : group_cur_before_,
340 cell, from, end, nullptr, nullptr, buffer_.isClean(), group_id_);
342 // fill in the real data to be saved
343 if (cell.inMathed()) {
344 // simply use the whole cell
345 MathData & ar = cell.cell();
346 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
348 // some more effort needed here as 'the whole cell' of the
349 // main Text _is_ the whole document.
350 // record the relevant paragraphs
351 Text const * text = cell.text();
353 ParagraphList const & plist = text->paragraphs();
354 ParagraphList::const_iterator first = plist.begin();
355 advance(first, first_pit);
356 ParagraphList::const_iterator last = plist.begin();
357 advance(last, last_pit + 1);
358 undo.pars = new ParagraphList(first, last);
361 // push the undo entry to undo stack
363 //lyxerr << "undo record: " << stack.top() << endl;
367 void Undo::Private::recordUndo(UndoKind kind,
368 DocIterator const & cell,
369 pit_type first_pit, pit_type last_pit,
370 CursorData const & cur)
372 LASSERT(first_pit <= cell.lastpit(), return);
373 LASSERT(last_pit <= cell.lastpit(), return);
375 doRecordUndo(kind, cell, first_pit, last_pit, cur,
378 // next time we'll try again to combine entries if possible
379 undo_finished_ = false;
381 // If we ran recordUndo, it means that we plan to change the buffer
388 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
389 UndoElementStack & stack)
392 LYXERR0("There is no group open (creating one)");
396 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id_);
397 // create the position information of the Undo entry
398 UndoElement undo(group_cur_before_.empty() ? cur_before : group_cur_before_,
399 buffer_.params(), buffer_.isClean(),
402 // push the undo entry to undo stack
407 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
409 doRecordUndoBufferParams(cur, undostack_);
411 // next time we'll try again to combine entries if possible
412 undo_finished_ = false;
414 // If we ran recordUndo, it means that we plan to change the buffer
421 void Undo::Private::doUndoRedoAction(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
423 // Adjust undo stack and get hold of current undo data.
424 UndoElement & undo = stack.top();
425 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
426 // We'll pop the stack only when we're done with this element. So do NOT
427 // try to return early.
429 // We will store in otherstack the part of the document under 'undo'
430 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
433 doRecordUndoBufferParams(undo.cur_after, otherstack);
435 LATTEST(undo.end <= cell_dit.lastpit());
436 doRecordUndo(ATOMIC_UNDO, cell_dit,
437 undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
440 otherstack.top().cur_after = undo.cur_before;
442 // This does the actual undo/redo.
443 //LYXERR0("undo, performing: " << undo);
444 DocIterator dit = undo.cell.asDocIterator(&buffer_);
446 // This is a params undo element
447 delete otherstack.top().bparams;
448 otherstack.top().bparams = new BufferParams(buffer_.params());
449 DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
450 buffer_.params() = *undo.bparams;
451 // The error list is not supposed to be helpful here.
453 cap::switchBetweenClasses(olddc, buffer_.params().documentClassPtr(),
454 static_cast<InsetText &>(buffer_.inset()), el);
456 } else if (dit.inMathed()) {
457 // We stored the full cell here as there is not much to be
458 // gained by storing just 'a few' paragraphs (most if not
459 // all math inset cells have just one paragraph!)
460 //LYXERR0("undo.array: " << *undo.array);
462 dit.cell().swap(*undo.array);
463 dit.inset().setBuffer(buffer_);
465 undo.array = nullptr;
467 // Some finer machinery is needed here.
468 Text * text = dit.text();
471 ParagraphList & plist = text->paragraphs();
473 // remove new stuff between first and last
474 ParagraphList::iterator first = plist.begin();
475 advance(first, undo.from);
476 ParagraphList::iterator last = plist.begin();
477 advance(last, plist.size() - undo.end);
478 plist.erase(first, last);
480 // re-insert old stuff instead
481 first = plist.begin();
482 advance(first, undo.from);
484 // this ugly stuff is needed until we get rid of the
485 // inset_owner backpointer
486 ParagraphList::iterator pit = undo.pars->begin();
487 ParagraphList::iterator const end = undo.pars->end();
488 for (; pit != end; ++pit)
489 pit->setInsetOwner(dit.realInset());
490 plist.insert(first, undo.pars->begin(), undo.pars->end());
492 // set the buffers for insets we created
493 ParagraphList::iterator fpit = plist.begin();
494 advance(fpit, undo.from);
495 ParagraphList::iterator fend = fpit;
496 advance(fend, undo.pars->size());
497 for (; fpit != fend; ++fpit)
498 fpit->setInsetBuffers(buffer_);
504 // We'll clean up in release mode.
505 LASSERT(undo.pars == nullptr, undo.pars = nullptr);
506 LASSERT(undo.array == nullptr, undo.array = nullptr);
508 if (!undo.cur_before.empty())
509 cur = undo.cur_before;
514 // Now that we're done with undo, we pop it off the stack.
519 bool Undo::Private::undoRedoAction(CursorData & cur, bool isUndoOperation)
521 undo_finished_ = true;
523 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
529 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
531 const size_t gid = stack.top().group_id;
532 while (!stack.empty() && stack.top().group_id == gid)
533 doUndoRedoAction(cur, stack, otherstack);
538 void Undo::finishUndo()
540 // Make sure the next operation will be stored.
541 d->undo_finished_ = true;
545 bool Undo::undoAction(CursorData & cur)
547 return d->undoRedoAction(cur, true);
551 bool Undo::redoAction(CursorData & cur)
553 return d->undoRedoAction(cur, false);
557 void Undo::beginUndoGroup()
559 if (d->group_level_ == 0) {
560 // create a new group
562 LYXERR(Debug::UNDO, "+++++++ Creating new group " << d->group_id_
563 << " for buffer " << &d->buffer_);
569 void Undo::beginUndoGroup(CursorData const & cur_before)
572 if (d->group_cur_before_.empty())
573 d->group_cur_before_ = cur_before;
577 void Undo::endUndoGroup()
579 if (d->group_level_ == 0) {
580 LYXERR0("There is no undo group to end here");
584 if (d->group_level_ == 0) {
585 // real end of the group
586 d->group_cur_before_ = CursorData();
587 LYXERR(Debug::UNDO, "------- End of group " << d->group_id_
588 << " of buffer " << &d->buffer_);
593 void Undo::endUndoGroup(CursorData const & cur_after)
596 if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
597 d->undostack_.top().cur_after = cur_after;
601 void Undo::splitUndoGroup(CursorData const & cur)
603 size_t const level = d->group_level_;
607 d->group_level_ = level;
611 bool Undo::activeUndoGroup() const
613 return d->group_level_ > 0
614 && !d->undostack_.empty()
615 && d->undostack_.top().group_id == d->group_id_;
619 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
621 d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
625 void Undo::recordUndo(CursorData const & cur, pit_type from, pit_type to)
627 d->recordUndo(ATOMIC_UNDO, cur, from, to, cur);
631 void Undo::recordUndoInset(CursorData const & cur, Inset const * inset)
633 if (!inset || inset == &cur.inset()) {
636 d->recordUndo(ATOMIC_UNDO, c, c.pit(), c.pit(), cur);
637 } else if (inset == cur.nextInset())
640 LYXERR0("Inset not found, no undo stack added.");
644 void Undo::recordUndoBufferParams(CursorData const & cur)
646 d->recordUndoBufferParams(cur);
650 void Undo::recordUndoFullBuffer(CursorData const & cur)
652 // This one may happen outside of the main undo group, so we
653 // put it in its own subgroup to avoid complaints.
655 d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
656 0, d->buffer_.paragraphs().size() - 1, cur);
657 d->recordUndoBufferParams(cur);
661 /// UndoGroupHelper class stuff
663 class UndoGroupHelper::Impl {
664 friend class UndoGroupHelper;
665 set<Buffer *> buffers_;
669 UndoGroupHelper::UndoGroupHelper(Buffer * buf) : d(new UndoGroupHelper::Impl)
675 UndoGroupHelper::~UndoGroupHelper()
677 for (Buffer * buf : d->buffers_)
678 if (theBufferList().isLoaded(buf) || theBufferList().isInternal(buf))
679 buf->undo().endUndoGroup();
683 void UndoGroupHelper::resetBuffer(Buffer * buf)
685 if (buf && d->buffers_.count(buf) == 0) {
686 d->buffers_.insert(buf);
687 buf->undo().beginUndoGroup();