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 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())
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())
93 UndoElement(UndoElement const & ue) : time(current_time())
96 cur_before = ue.cur_before;
97 cur_after = ue.cur_after;
104 ? new BufferParams(*ue.bparams) : 0;
105 lyx_clean = ue.lyx_clean;
106 group_id = ue.group_id;
114 /// Which kind of operation are we recording for?
116 /// the position of the cursor before recordUndo
117 CursorData cur_before;
118 /// the position of the cursor at the end of the undo group
119 CursorData cur_after;
120 /// the position of the cell described
121 StableDocIterator cell;
122 /// counted from begin of cell
124 /// complement to end of this cell
126 /// the contents of the saved Paragraphs (for texted)
127 ParagraphList * pars;
128 /// the contents of the saved MathData (for mathed)
130 /// Only used in case of params undo
131 BufferParams const * bparams;
132 /// Was the buffer clean at this point?
134 /// the element's group id
139 /// Protect construction
144 class UndoElementStack
147 /// limit is the maximum size of the stack
148 UndoElementStack(size_t limit = 100) { limit_ = limit; }
149 /// limit is the maximum size of the stack
150 ~UndoElementStack() { clear(); }
152 /// Return the top element.
153 UndoElement & top() { return c_.front(); }
155 /// Pop and throw away the top element.
156 void pop() { c_.pop_front(); }
158 /// Return true if the stack is empty.
159 bool empty() const { return c_.empty(); }
161 /// Clear all elements, deleting them.
163 for (size_t i = 0; i != c_.size(); ++i) {
170 /// Push an item on to the stack, deleting the bottom group on
172 void push(UndoElement const & v) {
173 // Remove some entries if the limit has been reached.
174 // However, if the only group on the stack is the one
175 // we are currently populating, do nothing.
176 if (c_.size() >= limit_
177 && c_.front().group_id != v.group_id) {
178 // remove a whole group at once.
179 const size_t gid = c_.back().group_id;
180 while (!c_.empty() && c_.back().group_id == gid)
186 /// Mark all the elements of the stack as dirty
188 for (size_t i = 0; i != c_.size(); ++i)
189 c_[i].lyx_clean = false;
193 /// Internal contents.
194 std::deque<UndoElement> c_;
195 /// The maximum number elements stored.
202 Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true),
203 group_id_(0), group_level_(0) {}
205 // Do one undo/redo step
206 void doUndoRedoAction(CursorData & cur, UndoElementStack & stack,
207 UndoElementStack & otherStack);
208 // Apply one undo/redo group. Returns false if no undo possible.
209 bool undoRedoAction(CursorData & cur, bool isUndoOperation);
212 void doRecordUndo(UndoKind kind,
213 DocIterator const & cell,
216 CursorData const & cur,
217 UndoElementStack & stack);
219 void recordUndo(UndoKind kind,
220 DocIterator const & cell,
223 CursorData const & cur);
225 void doRecordUndoBufferParams(CursorData const & cur, UndoElementStack & stack);
227 void recordUndoBufferParams(CursorData const & cur);
232 UndoElementStack undostack_;
234 UndoElementStack redostack_;
236 /// The flag used by Undo::finishUndo().
239 /// Current group Id.
241 /// Current group nesting nevel.
243 /// the position of cursor before the group was created
244 CursorData group_cur_before_;
248 /////////////////////////////////////////////////////////////////////
252 /////////////////////////////////////////////////////////////////////
255 Undo::Undo(Buffer & buffer)
256 : d(new Undo::Private(buffer))
268 d->undostack_.clear();
269 d->redostack_.clear();
270 d->undo_finished_ = true;
271 // We used to do that, but I believe it is better to keep
272 // groups (only used in Buffer::reload for now (JMarc)
274 //d->group_level_ = 0;
278 bool Undo::hasUndoStack() const
280 return !d->undostack_.empty();
284 bool Undo::hasRedoStack() const
286 return !d->redostack_.empty();
290 void Undo::markDirty()
292 d->undo_finished_ = true;
293 d->undostack_.markDirty();
294 d->redostack_.markDirty();
298 /////////////////////////////////////////////////////////////////////
302 ///////////////////////////////////////////////////////////////////////
304 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
306 StableDocIterator tmpi2 = i2;
307 tmpi2.pos() = i1.pos();
312 void Undo::Private::doRecordUndo(UndoKind kind,
313 DocIterator const & cell,
314 pit_type first_pit, pit_type last_pit,
315 CursorData const & cur_before,
316 UndoElementStack & stack)
319 LYXERR0("There is no group open (creating one)");
323 if (first_pit > last_pit)
324 swap(first_pit, last_pit);
326 // Undo::ATOMIC are always recorded (no overlapping there).
327 // As nobody wants all removed character appear one by one when undoing,
328 // we want combine 'similar' non-ATOMIC undo recordings to one.
329 pit_type from = first_pit;
330 pit_type end = cell.lastpit() - last_pit;
332 && kind != ATOMIC_UNDO
334 && !stack.top().bparams
335 && samePar(stack.top().cell, cell)
336 && stack.top().kind == kind
337 && stack.top().from == from
338 && stack.top().end == end
339 && stack.top().cur_after == cur_before
340 && current_time() - stack.top().time <= 2) {
341 // reset cur_after; it will be filled correctly by endUndoGroup.
342 stack.top().cur_after = CursorData();
343 // update the timestamp of the undo element
344 stack.top().time = current_time();
348 LYXERR(Debug::UNDO, "Create undo element of group " << group_id_);
349 // create the position information of the Undo entry
350 UndoElement undo(kind,
351 group_cur_before_.empty() ? cur_before : group_cur_before_,
352 cell, from, end, 0, 0, buffer_.isClean(), group_id_);
354 // fill in the real data to be saved
355 if (cell.inMathed()) {
356 // simply use the whole cell
357 MathData & ar = cell.cell();
358 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
360 // some more effort needed here as 'the whole cell' of the
361 // main Text _is_ the whole document.
362 // record the relevant paragraphs
363 Text const * text = cell.text();
365 ParagraphList const & plist = text->paragraphs();
366 ParagraphList::const_iterator first = plist.begin();
367 advance(first, first_pit);
368 ParagraphList::const_iterator last = plist.begin();
369 advance(last, last_pit + 1);
370 undo.pars = new ParagraphList(first, last);
373 // push the undo entry to undo stack
375 //lyxerr << "undo record: " << stack.top() << endl;
379 void Undo::Private::recordUndo(UndoKind kind,
380 DocIterator const & cell,
381 pit_type first_pit, pit_type last_pit,
382 CursorData const & cur)
384 LASSERT(first_pit <= cell.lastpit(), return);
385 LASSERT(last_pit <= cell.lastpit(), return);
387 doRecordUndo(kind, cell, first_pit, last_pit, cur,
390 // next time we'll try again to combine entries if possible
391 undo_finished_ = false;
393 // If we ran recordUndo, it means that we plan to change the buffer
400 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
401 UndoElementStack & stack)
404 LYXERR0("There is no group open (creating one)");
408 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id_);
409 // create the position information of the Undo entry
410 UndoElement undo(group_cur_before_.empty() ? cur_before : group_cur_before_,
411 buffer_.params(), buffer_.isClean(),
414 // push the undo entry to undo stack
419 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
421 doRecordUndoBufferParams(cur, undostack_);
423 // next time we'll try again to combine entries if possible
424 undo_finished_ = false;
426 // If we ran recordUndo, it means that we plan to change the buffer
433 void Undo::Private::doUndoRedoAction(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
435 // Adjust undo stack and get hold of current undo data.
436 UndoElement & undo = stack.top();
437 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
438 // We'll pop the stack only when we're done with this element. So do NOT
439 // try to return early.
441 // We will store in otherstack the part of the document under 'undo'
442 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
445 doRecordUndoBufferParams(undo.cur_after, otherstack);
447 LATTEST(undo.end <= cell_dit.lastpit());
448 doRecordUndo(ATOMIC_UNDO, cell_dit,
449 undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
452 otherstack.top().cur_after = undo.cur_before;
454 // This does the actual undo/redo.
455 //LYXERR0("undo, performing: " << undo);
456 DocIterator dit = undo.cell.asDocIterator(&buffer_);
458 // This is a params undo element
459 delete otherstack.top().bparams;
460 otherstack.top().bparams = new BufferParams(buffer_.params());
461 DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
462 buffer_.params() = *undo.bparams;
463 // The error list is not supposed to be helpful here.
465 cap::switchBetweenClasses(olddc, buffer_.params().documentClassPtr(),
466 static_cast<InsetText &>(buffer_.inset()), el);
468 } else if (dit.inMathed()) {
469 // We stored the full cell here as there is not much to be
470 // gained by storing just 'a few' paragraphs (most if not
471 // all math inset cells have just one paragraph!)
472 //LYXERR0("undo.array: " << *undo.array);
474 dit.cell().swap(*undo.array);
475 dit.inset().setBuffer(buffer_);
479 // Some finer machinery is needed here.
480 Text * text = dit.text();
483 ParagraphList & plist = text->paragraphs();
485 // remove new stuff between first and last
486 ParagraphList::iterator first = plist.begin();
487 advance(first, undo.from);
488 ParagraphList::iterator last = plist.begin();
489 advance(last, plist.size() - undo.end);
490 plist.erase(first, last);
492 // re-insert old stuff instead
493 first = plist.begin();
494 advance(first, undo.from);
496 // this ugly stuff is needed until we get rid of the
497 // inset_owner backpointer
498 ParagraphList::iterator pit = undo.pars->begin();
499 ParagraphList::iterator const end = undo.pars->end();
500 for (; pit != end; ++pit)
501 pit->setInsetOwner(dit.realInset());
502 plist.insert(first, undo.pars->begin(), undo.pars->end());
504 // set the buffers for insets we created
505 ParagraphList::iterator fpit = plist.begin();
506 advance(fpit, undo.from);
507 ParagraphList::iterator fend = fpit;
508 advance(fend, undo.pars->size());
509 for (; fpit != fend; ++fpit)
510 fpit->setInsetBuffers(buffer_);
516 // We'll clean up in release mode.
517 LASSERT(undo.pars == 0, undo.pars = 0);
518 LASSERT(undo.array == 0, undo.array = 0);
520 if (!undo.cur_before.empty())
521 cur = undo.cur_before;
526 // Now that we're done with undo, we pop it off the stack.
531 bool Undo::Private::undoRedoAction(CursorData & cur, bool isUndoOperation)
533 undo_finished_ = true;
535 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
541 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
543 const size_t gid = stack.top().group_id;
544 while (!stack.empty() && stack.top().group_id == gid)
545 doUndoRedoAction(cur, stack, otherstack);
550 void Undo::finishUndo()
552 // Make sure the next operation will be stored.
553 d->undo_finished_ = true;
557 bool Undo::undoAction(CursorData & cur)
559 return d->undoRedoAction(cur, true);
563 bool Undo::redoAction(CursorData & cur)
565 return d->undoRedoAction(cur, false);
569 void Undo::beginUndoGroup()
571 if (d->group_level_ == 0) {
572 // create a new group
574 LYXERR(Debug::UNDO, "+++++++ Creating new group " << d->group_id_
575 << " for buffer " << &d->buffer_);
581 void Undo::beginUndoGroup(CursorData const & cur_before)
584 if (d->group_cur_before_.empty())
585 d->group_cur_before_ = cur_before;
589 void Undo::endUndoGroup()
591 if (d->group_level_ == 0) {
592 LYXERR0("There is no undo group to end here");
596 if (d->group_level_ == 0) {
597 // real end of the group
598 d->group_cur_before_ = CursorData();
599 LYXERR(Debug::UNDO, "------- End of group " << d->group_id_
600 << " of buffer " << &d->buffer_);
605 void Undo::endUndoGroup(CursorData const & cur_after)
608 if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
609 d->undostack_.top().cur_after = cur_after;
613 bool Undo::activeUndoGroup() const
615 return d->group_level_ > 0
616 && !d->undostack_.empty()
617 && d->undostack_.top().group_id == d->group_id_;
621 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
623 d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
627 void Undo::recordUndo(CursorData const & cur, pit_type from, pit_type to)
629 d->recordUndo(ATOMIC_UNDO, cur, from, to, cur);
633 void Undo::recordUndoInset(CursorData const & cur, Inset const * inset)
635 if (!inset || inset == &cur.inset()) {
638 d->recordUndo(ATOMIC_UNDO, c, c.pit(), c.pit(), cur);
639 } else if (inset == cur.nextInset())
642 LYXERR0("Inset not found, no undo stack added.");
646 void Undo::recordUndoBufferParams(CursorData const & cur)
648 d->recordUndoBufferParams(cur);
652 void Undo::recordUndoFullBuffer(CursorData const & cur)
654 // This one may happen outside of the main undo group, so we
655 // put it in its own subgroup to avoid complaints.
657 d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
658 0, d->buffer_.paragraphs().size() - 1, cur);
659 d->recordUndoBufferParams(cur);
663 /// UndoGroupHelper class stuff
665 class UndoGroupHelper::Impl {
666 friend class UndoGroupHelper;
667 set<Buffer *> buffers_;
671 UndoGroupHelper::UndoGroupHelper(Buffer * buf) : d(new UndoGroupHelper::Impl)
677 UndoGroupHelper::~UndoGroupHelper()
679 for (Buffer * buf : d->buffers_)
680 if (theBufferList().isLoaded(buf) || theBufferList().isInternal(buf))
681 buf->undo().endUndoGroup();
685 void UndoGroupHelper::resetBuffer(Buffer * buf)
687 if (buf && d->buffers_.count(buf) == 0) {
688 d->buffers_.insert(buf);
689 buf->undo().beginUndoGroup();